Skip to content

Commit e3757ed

Browse files
authored
feat(file_system): Add remove(path) and remove_contents(path) (#267)
* feat(file_system): Add remove(path) and remove_contents(path) * update readme.
1 parent c044871 commit e3757ed

File tree

4 files changed

+136
-19
lines changed

4 files changed

+136
-19
lines changed

components/file_system/example/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,5 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui
2626

2727
## Example Output
2828

29-
![CleanShot 2024-06-28 at 09 08 19](https://github.com/esp-cpp/espp/assets/213467/ae2791b0-e240-4a55-808d-fed00335b4e4)
29+
![CleanShot 2024-07-01 at 13 57 50](https://github.com/esp-cpp/espp/assets/213467/dd45e8eb-6927-403b-a889-86274cfeeb76)
3030

components/file_system/example/main/file_system_example.cpp

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ extern "C" void app_main(void) {
3939
}
4040

4141
const std::string_view test_dir = "sandbox";
42+
const std::string_view sub_dir = "subdir";
4243
const std::string_view test_file = "test.csv";
4344
const std::string_view file_contents = "Hello World!";
4445
// use posix api
@@ -108,6 +109,22 @@ extern "C" void app_main(void) {
108109
logger.info("Renamed '{}' to '{}'", file, file2);
109110
}
110111

112+
// make a subdirectory
113+
std::string sub = sandbox + "/" + std::string(sub_dir);
114+
mkdir(sub.c_str(), 0755);
115+
logger.info("Created subdirectory {}", sub);
116+
117+
// make a file in the subdirectory
118+
std::string sub_file = sub + "/subfile.txt";
119+
FILE *sub_fp = fopen(sub_file.c_str(), "w");
120+
if (sub_fp == nullptr) {
121+
logger.error("Couldn't open {} for writing!", sub_file);
122+
} else {
123+
fwrite(file_contents.data(), 1, file_contents.size(), sub_fp);
124+
fclose(sub_fp);
125+
logger.info("Wrote '{}' to {}", file_contents, sub_file);
126+
}
127+
111128
// list files in a directory
112129
auto &fs = espp::FileSystem::get();
113130
espp::FileSystem::ListConfig config;
@@ -146,7 +163,7 @@ extern "C" void app_main(void) {
146163
}
147164

148165
// cleanup
149-
auto items = {file, file2, sandbox};
166+
auto items = {sub_file, sub, file, file2, sandbox};
150167
for (auto &item : items) {
151168
// use stat to figure out if it exists
152169
auto code = stat(item.c_str(), &st);
@@ -234,6 +251,24 @@ extern "C" void app_main(void) {
234251
logger.info("Renamed {} to {}", file.string(), file2.string());
235252
}
236253

254+
// make a subdirectory
255+
fs::path sub = sandbox / fs::path{sub_dir};
256+
fs::create_directory(sub, ec);
257+
if (ec) {
258+
logger.error("Could not create directory {} - {}", sub.string(), ec.message());
259+
} else {
260+
logger.info("Created subdirectory {}", sub.string());
261+
}
262+
263+
// make a file in the subdirectory
264+
fs::path sub_file = sub / "subfile.txt";
265+
std::ofstream sub_ofs(sub_file);
266+
sub_ofs << file_contents;
267+
sub_ofs.close();
268+
sub_ofs.flush();
269+
logger.info("Wrote '{}' to {}", file_contents, sub_file.string());
270+
271+
// list files in a directory
237272
logger.info("Directory iterator:");
238273
logger.warn(
239274
"NOTE: directory_iterator is not implemented in esp-idf right now :( (as of v5.2.2)");
@@ -247,23 +282,30 @@ extern "C" void app_main(void) {
247282
logger.info("\tThis is expected since directory_iterator is not implemented in esp-idf.");
248283
}
249284

250-
// cleanup
251-
auto items = {file, file2, sandbox};
252-
for (const auto &item : items) {
253-
if (!fs::exists(item)) {
254-
logger.warn("Not removing '{}', it doesn't exist!", item.string());
255-
continue;
256-
}
257-
// and if it is a directory
258-
bool is_dir = fs::is_directory(item);
259-
auto err = is_dir ? rmdir(item.string().c_str()) : unlink(item.string().c_str());
260-
if (err) {
261-
logger.error("Could not remove {}, error code: {}", item.string(), err);
262-
} else {
263-
logger.info("Cleaned up {}", item.string());
264-
}
265-
// fs::remove(item, ec); // NOTE: cannot use fs::remove since it seems POSIX remove()
266-
// doesn't work
285+
logger.info("Recursive directory listing:");
286+
auto &espp_fs = espp::FileSystem::get();
287+
auto files = espp_fs.get_files_in_path(sandbox, true, true);
288+
for (const auto &f : files) {
289+
logger.info("\t{}", f);
290+
}
291+
292+
// cleanup, use convenience functions
293+
// NOTE: cannot use fs::remove since it seems POSIX remove() doesn't work
294+
// We'll use espp::FileSystem::remove, which works for both files and
295+
// directories, and will recursively remove all the contents of the
296+
// directory.
297+
espp_fs.set_log_level(espp::Logger::Verbosity::DEBUG);
298+
if (!espp_fs.remove(sandbox, ec)) {
299+
logger.error("Could not remove {}", sandbox.string());
300+
} else {
301+
logger.info("Cleaned up {}", sandbox.string());
302+
}
303+
304+
// now list entries in root (recursively) after cleanup
305+
logger.info("Recursive directory listing after cleanup:");
306+
files = espp_fs.get_files_in_path(espp_fs.get_root_path(), true, true);
307+
for (const auto &f : files) {
308+
logger.info("\t{}", f);
267309
}
268310
//! [file_system std filesystem example]
269311
}

components/file_system/include/file_system.hpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,27 @@ class FileSystem : public BaseComponent {
161161
bool include_directories = false,
162162
bool recursive = false);
163163

164+
/// @brief Completely remove a file or directory (including contents)
165+
/// @details This method removes a file or directory and all of its contents.
166+
/// If the path is a directory, it will iterate over the contents
167+
/// and remove them recursively. If the path is a file, it will
168+
/// remove the file. If the path does not exist, it will return false.
169+
/// @param path The path to the file or directory
170+
/// @param ec The error code to set if an error occurs
171+
/// @return Whether the file or directory was successfully removed
172+
bool remove(const std::filesystem::path &path, std::error_code &ec);
173+
174+
/// @brief Remove the contents of a directory, but not the directory itself
175+
/// @details This method removes the contents of a directory, but not the
176+
/// directory itself. If the path is not a directory, it will return
177+
/// false. If the path does not exist, it will return false. If the
178+
/// path is a directory, it will iterate over the contents and remove
179+
/// them recursively.
180+
/// @param path The path to the directory
181+
/// @param ec The error code to set if an error occurs
182+
/// @return Whether the contents of the directory were successfully removed
183+
bool remove_contents(const std::filesystem::path &path, std::error_code &ec);
184+
164185
/// @brief List the contents of a directory
165186
/// @details
166187
/// This method lists the contents of a directory. It returns a string
@@ -238,5 +259,19 @@ class FileSystem : public BaseComponent {
238259
/// This method initializes the file system. It is protected and called only by the constructor.
239260
/// It is responsible for mounting the file system and creating the root directory.
240261
void init();
262+
263+
/// @brief Remove a file
264+
/// @param path The path to the file
265+
/// @return Whether the file was successfully removed
266+
bool remove_file(const std::filesystem::path &path);
267+
268+
/// @brief Remove an empty directory
269+
/// @param path The path to the empty directory
270+
/// @return Whether the directory was successfully removed
271+
/// @note This method does not remove the contents of the directory, only the
272+
/// directory itself.
273+
/// @warning If the directory is not empty, it will not be removed, and the
274+
/// method will return false.
275+
bool remove_directory(const std::filesystem::path &path);
241276
};
242277
} // namespace espp

components/file_system/src/file_system.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,46 @@ std::vector<std::filesystem::path> FileSystem::get_files_in_path(const std::file
121121
return files;
122122
}
123123

124+
bool FileSystem::remove(const std::filesystem::path &path, std::error_code &ec) {
125+
logger_.debug("Removing path: {}", path.string());
126+
namespace fs = std::filesystem;
127+
auto file_status = fs::status(path, ec);
128+
if (std::filesystem::is_directory(file_status)) {
129+
if (!remove_contents(path, ec)) {
130+
logger_.error("Failed to remove contents of directory: {}", path.string());
131+
return false;
132+
}
133+
return remove_directory(path);
134+
} else {
135+
return remove_file(path);
136+
}
137+
}
138+
139+
bool FileSystem::remove_file(const std::filesystem::path &path) {
140+
logger_.debug("Removing file: {}", path.string());
141+
return unlink(path.c_str()) == 0;
142+
}
143+
144+
bool FileSystem::remove_directory(const std::filesystem::path &path) {
145+
logger_.debug("Removing directory: {}", path.string());
146+
return rmdir(path.c_str()) == 0;
147+
}
148+
149+
bool FileSystem::remove_contents(const std::filesystem::path &path, std::error_code &ec) {
150+
logger_.debug("Removing contents of directory: {}", path.string());
151+
bool include_directories = true;
152+
bool recursive = false; // don't want recursive since we'll already recurse
153+
auto files = get_files_in_path(path, include_directories, recursive);
154+
bool success = true;
155+
for (const auto &file : files) {
156+
if (!remove(file, ec)) {
157+
logger_.error("Failed to remove file: {}", file.string());
158+
success = false;
159+
}
160+
}
161+
return success;
162+
}
163+
124164
std::string FileSystem::list_directory(const std::string &path, const ListConfig &config,
125165
const std::string &prefix) {
126166
std::string result;

0 commit comments

Comments
 (0)