feat(watch): report correlated file renames

This commit is contained in:
2026-06-18 19:27:39 -05:00
parent 3c2cf22ecf
commit bf56b43190
3 changed files with 44 additions and 11 deletions

View File

@@ -71,7 +71,8 @@ if (library) {
auto watcher = izo::watch_directory(
{{"assets"}, true},
[](const izo::file_event& event) {
// Handle added, removed, or modified files.
// Handle added, removed, modified, or renamed files.
// Rename events provide the old path in event.previous_path.
},
&error);
```

View File

@@ -8,11 +8,12 @@
namespace izo {
enum class file_change { added, removed, modified };
enum class file_change { added, removed, modified, renamed };
struct file_event {
file_change change;
std::filesystem::path path;
std::filesystem::path previous_path;
};
struct directory_watch_options {

View File

@@ -4,11 +4,21 @@
#include <map>
#include <thread>
#include <utility>
#include <vector>
namespace izo {
namespace {
using snapshot = std::map<std::filesystem::path, std::filesystem::file_time_type>;
struct file_stamp {
std::filesystem::file_time_type write_time;
std::uintmax_t size;
bool operator==(const file_stamp& other) const {
return write_time == other.write_time && size == other.size;
}
bool operator!=(const file_stamp& other) const { return !(*this == other); }
};
using snapshot = std::map<std::filesystem::path, file_stamp>;
snapshot scan(const directory_watch_options& options) {
snapshot files;
@@ -16,7 +26,8 @@ snapshot scan(const directory_watch_options& options) {
auto add = [&](const std::filesystem::directory_entry& entry) {
if (!entry.is_regular_file(ec) || ec) { ec.clear(); return; }
const auto time = entry.last_write_time(ec);
if (!ec) files.emplace(entry.path(), time);
const auto size = entry.file_size(ec);
if (!ec) files.emplace(entry.path(), file_stamp{time, size});
ec.clear();
};
if (options.recursive) {
@@ -47,15 +58,35 @@ struct directory_watcher::implementation {
std::this_thread::sleep_for(options.poll_interval);
if (!running.load(std::memory_order_relaxed)) break;
auto current = scan(options);
for (const auto& item : current) {
const auto old = previous.find(item.first);
if (old == previous.end()) callback({file_change::added, item.first});
else if (old->second != item.second) callback({file_change::modified, item.first});
std::vector<snapshot::const_iterator> added;
std::vector<snapshot::const_iterator> removed;
for (auto item = current.begin(); item != current.end(); ++item) {
const auto old = previous.find(item->first);
if (old == previous.end()) added.push_back(item);
else if (old->second != item->second)
callback({file_change::modified, item->first, {}});
}
for (const auto& item : previous) {
if (current.find(item.first) == current.end())
callback({file_change::removed, item.first});
for (auto item = previous.begin(); item != previous.end(); ++item) {
if (current.find(item->first) == current.end()) removed.push_back(item);
}
std::vector<bool> matched_added(added.size(), false);
std::vector<bool> matched_removed(removed.size(), false);
for (std::size_t old_index = 0; old_index < removed.size(); ++old_index) {
for (std::size_t new_index = 0; new_index < added.size(); ++new_index) {
if (!matched_added[new_index] &&
removed[old_index]->second == added[new_index]->second) {
callback({file_change::renamed, added[new_index]->first,
removed[old_index]->first});
matched_added[new_index] = true;
matched_removed[old_index] = true;
break;
}
}
}
for (std::size_t i = 0; i < added.size(); ++i)
if (!matched_added[i]) callback({file_change::added, added[i]->first, {}});
for (std::size_t i = 0; i < removed.size(); ++i)
if (!matched_removed[i]) callback({file_change::removed, removed[i]->first, {}});
previous = std::move(current);
}
});