8#include <unordered_map>
9#include <unordered_set>
12#include <hpp/event.hpp>
13#define POOLSTL_STD_SUPPLEMENT 1
14#include <poolstl/poolstl.hpp>
18using namespace std::literals;
23void log_path(
const fs::path& )
43 for(
const auto& e : rhs.
entries)
48 auto created_sz_before =
created.size();
51 created.emplace_back(created_sz_before + idx);
54 auto modified_sz_before =
modified.size();
57 modified.emplace_back(modified_sz_before + idx);
64 for(
auto& e : rhs.entries)
66 entries.emplace_back(std::move(e));
69 auto created_sz_before =
created.size();
70 for(
auto idx : rhs.created)
72 created.emplace_back(created_sz_before + idx);
75 auto modified_sz_before =
modified.size();
76 for(
auto idx : rhs.modified)
78 modified.emplace_back(modified_sz_before + idx);
94 watcher::clock_t::duration poll_interval)
96 , poll_interval_(poll_interval)
97 , recursive_(recursive)
98 , init_time_timestamp_(
std::chrono::system_clock::now())
104 for(
auto&
entry : fs::recursive_directory_iterator(root_, err))
112 for(
auto&
entry : fs::directory_iterator(root_, err))
131 last_poll_ = watcher::clock_t::time_point{};
147 seen_keys_this_scan_.clear();
148 seen_keys_this_scan_.reserve(entries_.size());
151 if(!buffered_changes_.
entries.empty())
153 std::swap(changes, buffered_changes_);
159 for(
auto&
entry : fs::recursive_directory_iterator(root_, err))
167 for(
auto&
entry : fs::directory_iterator(root_, err))
187 buffered_changes_.
append(std::move(changes));
199 static auto get_original_path(
const fs::path& old_path,
const fs::path& renamed_path,
const fs::path& new_path) -> fs::path
201 fs::path relative_path = fs::relative(new_path, renamed_path);
202 fs::path original_path = old_path / relative_path;
203 return original_path;
208 bool same_extensions =
true;
213 while(ep.has_extension() || fp.has_extension())
215 same_extensions &= ep.extension() == fp.extension();
220 return same_extensions;
226 for(
const auto& renamed_idx : renamed_dirs)
228 const auto& renamed_e =
entries[renamed_idx];
234 e.event_time = std::chrono::system_clock::now();
245 std::unordered_map<uintmax_t, std::vector<std::string>>& missing_by_size)
247 auto size_it = missing_by_size.find(
size);
248 if(size_it == missing_by_size.end())
252 auto& candidates = size_it->second;
253 for(std::size_t i = 0; i < candidates.size(); ++i)
255 if(candidates[i] != key)
259 candidates[i] = candidates.back();
260 candidates.pop_back();
261 if(candidates.empty())
263 missing_by_size.erase(size_it);
269 template<
typename Container>
271 const std::unordered_set<std::string>& seen_keys,
272 std::unordered_map<uintmax_t, std::vector<std::string>>& missing_by_size)
274 for(
const auto& kvp : old_entries)
276 if(seen_keys.find(kvp.first) == seen_keys.end())
278 missing_by_size[kvp.second.size].push_back(kvp.first);
283 template<
typename Container>
285 Container& old_entries,
286 std::unordered_map<uintmax_t, std::vector<std::string>>& missing_by_size) ->
bool
288 auto size_it = missing_by_size.find(e.size);
289 if(size_it == missing_by_size.end())
294 auto& candidates = size_it->second;
295 for(std::size_t i = 0; i < candidates.size(); ++i)
297 const auto& key = candidates[i];
298 auto entry_it = old_entries.find(key);
299 if(entry_it == old_entries.end())
304 const auto& fi = entry_it->second;
305 auto diff = (e.last_mod_time - fi.last_mod_time);
306 auto d = std::chrono::duration_cast<std::chrono::milliseconds>(diff);
307 if(d > std::chrono::milliseconds(0))
317 e.last_path = fi.path;
318 e.event_time = std::chrono::system_clock::now();
319 old_entries.erase(entry_it);
321 candidates[i] = candidates.back();
322 candidates.pop_back();
323 if(candidates.empty())
325 missing_by_size.erase(size_it);
333 template<
typename Container>
335 Container& old_entries,
336 std::unordered_map<uintmax_t, std::vector<std::string>>& missing_by_size)
338 for(
auto& size_group : missing_by_size)
340 for(
const auto& key : size_group.second)
342 auto it = old_entries.find(key);
343 if(it == old_entries.end())
347 auto fi = it->second;
349 fi.event_time = std::chrono::system_clock::now();
350 entries.push_back(std::move(fi));
351 old_entries.erase(it);
356 template<
typename Container>
359 const std::unordered_set<std::string>& seen_keys,
360 const fs::path& listener_root)
363 std::vector<size_t> renamed_dirs;
364 std::unordered_map<uintmax_t, std::vector<std::string>> missing_by_size;
367 for(
auto idx : changes.
created)
369 auto& e = changes.
entries[idx];
373 const auto key = e.last_path.string();
374 auto old_it = old_entries.find(key);
375 if(old_it != old_entries.end())
378 old_entries.erase(old_it);
383 if(!missing_by_size.empty() &&
try_match_rename(e, old_entries, missing_by_size))
385 if(e.type == fs::file_type::directory)
387 renamed_dirs.emplace_back(idx);
408 auto time =
entry.last_write_time( err);
410 fs::file_status status =
entry.status( err);
411 std::string key =
entry.path().string();
412 seen_keys_this_scan_.insert(key);
413 auto it = entries_.find(key);
414 if(it != entries_.end())
416 auto& fi = it->second;
418 if(fi.last_mod_time != time || fi.size !=
size || fi.type != status.type())
421 fi.last_mod_time = time;
423 fi.type = status.type();
428 fi.event_time = last_mod_time;
436 fi.type = status.type();
442 auto& fi = entries_[key];
443 fi.path =
entry.path();
444 fi.last_path =
entry.path();
445 fi.last_mod_time = time;
448 fi.type = status.type();
451 fi.event_time = std::chrono::system_clock::now();
458 hpp::event<void(
const std::vector<watcher::entry>&)>
on_changes;
476 std::map<std::string, watcher::entry> entries_;
478 std::chrono::system_clock::time_point init_time_timestamp_;
480 watcher::clock_t::duration poll_interval_ = 500ms;
482 watcher::clock_t::time_point last_poll_ = watcher::clock_t::now();
484 bool recursive_ =
false;
486 std::atomic<bool> paused_ = {
false};
488 std::unordered_set<std::string> seen_keys_this_scan_;
490 observed_changes buffered_changes_;
500 watcher::clock_t::duration poll_interval,
502 std::shared_ptr<directory_listener> listener,
503 const std::string& watcher_name)
506 , recursive_(recursive)
507 , callback_(
std::move(callback))
508 , listener_(
std::move(listener))
509 , init_time_timestamp_(
std::chrono::system_clock::now())
510 , watcher_name_(watcher_name)
513 initialize_entries(initial_list);
516 slot_key_ = listener_->on_changes.connect([
this](
const std::vector<watcher::entry>& changes) ->
void
518 handle_changes(changes);
526 listener_->on_changes.disconnect(slot_key_);
551 void poll_entry(
const fs::directory_entry&
entry, std::vector<watcher::entry>& initial_entries,
bool emit_initial_list)
555 fs::file_status file_status =
entry.status(err2);
557 auto file_type = file_status.type();
558 if(filter_passed || (file_type == fs::file_type::directory))
561 e.path =
entry.path();
562 e.last_path =
entry.path();
566 e.last_mod_time =
entry.last_write_time(err3);
567 e.size =
entry.file_size(err3);
571 e.event_time = std::chrono::system_clock::now();
574 std::string key = e.path.string();
576 if(emit_initial_list && filter_passed)
578 initial_entries.push_back(e);
584 void initialize_entries(
bool emit_initial_list)
588 std::vector<watcher::entry> initial_entries;
592 for(
auto&
entry : fs::recursive_directory_iterator(path_, err))
594 poll_entry(
entry, initial_entries, emit_initial_list);
599 for(
auto&
entry : fs::directory_iterator(path_, err))
601 poll_entry(
entry, initial_entries, emit_initial_list);
606 if(emit_initial_list && !initial_entries.empty() && callback_)
608 callback_(initial_entries,
true);
612 auto get_system_timestamp(
const watcher::entry&
entry) -> std::chrono::system_clock::time_point
616 return entry.event_time;
622 void handle_changes(
const std::vector<watcher::entry>& changes)
625 std::vector<watcher::entry> filtered_changes;
627 for(
const auto&
entry : changes)
630 if(!is_path_under_watch(
entry.path))
642 auto system_timestamp = get_system_timestamp(
entry);
644 if(system_timestamp < init_time_timestamp_)
649 filtered_changes.push_back(
entry);
652 if(filtered_changes.empty())
661 buffered_changes_.insert(buffered_changes_.end(), filtered_changes.begin(), filtered_changes.end());
668 callback_(filtered_changes,
false);
672 auto is_path_under_watch(
const fs::path& event_path)
const ->
bool
675 fs::path watch_path = fs::weakly_canonical(path_, ec);
678 watch_path = fs::absolute(path_, ec);
683 watch_path = watch_path.lexically_normal();
685 fs::path resolved_event_path = fs::weakly_canonical(event_path, ec);
688 resolved_event_path = fs::absolute(event_path, ec);
691 resolved_event_path = event_path;
693 resolved_event_path = resolved_event_path.lexically_normal();
695 if(resolved_event_path == watch_path)
703 pattern_filter filter_;
706 std::shared_ptr<directory_listener> listener_;
708 std::chrono::system_clock::time_point init_time_timestamp_;
709 uint64_t slot_key_ = 0;
710 std::atomic<bool> paused_ =
false;
711 std::vector<watcher::entry> buffered_changes_;
712 std::string watcher_name_;
722 std::lock_guard<std::mutex> lock(
mutex_);
737 std::lock_guard<std::mutex> lock(
mutex_);
741 kvp.second->resume();
742 kvp.second->request_immediate_poll();
746 kvp.second->resume();
755 std::this_thread::sleep_for(duration);
778 using namespace std::literals;
783 std::unique_lock<std::mutex> lock(
mutex_);
784 cv_.wait_for(lock, 500ms);
788 watcher::clock_t::duration sleep_time = 99999h;
791 std::map<fs::path, std::shared_ptr<directory_listener>> listeners;
793 std::unique_lock<std::mutex> lock(
mutex_);
798 for(
auto& pair : listeners)
800 auto listener = pair.second;
802 auto now = watcher::clock_t::now();
804 auto diff = (listener->last_poll_ + listener->poll_interval_) - now;
805 if(diff <= watcher::clock_t::duration(0))
808 listener->last_poll_ = now;
810 sleep_time = std::min(sleep_time, listener->poll_interval_);
814 sleep_time = std::min(sleep_time, diff);
818 std::unique_lock<std::mutex> lock(
mutex_);
819 cv_.wait_for(lock, sleep_time);
828 watcher::clock_t::duration poll_interval,
830 const std::string& watcher_name
838 std::shared_ptr<directory_listener> listener;
840 fs::path abs_path = fs::absolute(path, err);
843 abs_path = abs_path.lexically_normal();
845 bool is_new_listener =
false;
848 std::lock_guard<std::mutex> lock(mutex_);
850 auto it = directory_listeners_.find(abs_path);
851 if(it != directory_listeners_.end())
853 listener = it->second;
857 for(
auto& [watched_path, existing_listener] : directory_listeners_)
861 listener = existing_listener;
868 listener = std::make_shared<directory_listener>(abs_path, recursive, poll_interval);
870 is_new_listener =
true;
874 static std::atomic<std::uint64_t> free_id = {1};
875 auto key = free_id++;
876 auto impl = std::make_shared<watcher_fallback::impl>(path, filter, recursive, initial_list, poll_interval, std::move(callback), listener, watcher_name);
878 std::lock_guard<std::mutex> lock(mutex_);
879 watchers_[key] =
impl;
882 directory_listeners_[abs_path] = listener;
898 const auto& listener = it->second;
899 const bool referenced = std::any_of(
watchers_.begin(),
901 [&listener](
const auto& kvp) ->
bool
903 return kvp.second && kvp.second->get_listener() == listener;
919 std::lock_guard<std::mutex> lock(
mutex_);
929 std::lock_guard<std::mutex> lock(
mutex_);
A filter that combines include and exclude patterns for file/directory filtering.
auto should_include(const fs::path &path) const -> bool
Tests if a path should be included based on the filter rules Logic: (matches any include pattern OR n...
static void emit_remaining_removed(std::vector< watcher::entry > &entries, Container &old_entries, std::unordered_map< uintmax_t, std::vector< std::string > > &missing_by_size)
auto get_path() const -> const fs::path &
directory_listener(const fs::path &path, bool recursive, watcher::clock_t::duration poll_interval)
static auto try_match_rename(watcher::entry &e, Container &old_entries, std::unordered_map< uintmax_t, std::vector< std::string > > &missing_by_size) -> bool
static void remove_missing_candidate(const std::string &key, uintmax_t size, std::unordered_map< uintmax_t, std::vector< std::string > > &missing_by_size)
static void collect_missing_entries(const Container &old_entries, const std::unordered_set< std::string > &seen_keys, std::unordered_map< uintmax_t, std::vector< std::string > > &missing_by_size)
static auto get_original_path(const fs::path &old_path, const fs::path &renamed_path, const fs::path &new_path) -> fs::path
static void process_modifications(Container &old_entries, observed_changes &changes, const std::unordered_set< std::string > &seen_keys, const fs::path &listener_root)
void request_immediate_poll()
hpp::event< void(const std::vector< watcher::entry > &)> on_changes
Event that emits changes to all connected impls.
void poll_entry(const fs::directory_entry &entry, observed_changes &changes)
auto get_recursive() const -> bool
static auto check_if_parent_dir_was_renamed(const std::vector< size_t > &renamed_dirs, const std::vector< watcher::entry > &entries, watcher::entry &e) -> bool
static auto check_if_same_extension(const fs::path &p1, const fs::path &p2) -> bool
impl(const fs::path &path, const pattern_filter &filter, bool recursive, bool initial_list, watcher::clock_t::duration poll_interval, watcher::notify_callback callback, std::shared_ptr< directory_listener > listener, const std::string &watcher_name)
auto get_listener() const -> std::shared_ptr< directory_listener >
auto get_path() const -> const fs::path &
std::thread thread_
Thread that polls for changes.
auto watch_impl(const fs::path &path, const pattern_filter &filter, bool recursive, bool initial_list, watcher::clock_t::duration poll_interval, watcher::notify_callback callback, const std::string &watcher_name) -> std::uint64_t
std::atomic< bool > globally_paused_
std::atomic< bool > watching_
Atomic bool sync.
std::map< fs::path, std::shared_ptr< directory_listener > > directory_listeners_
void prune_stale_listeners()
std::condition_variable cv_
std::map< std::uint64_t, std::shared_ptr< impl > > watchers_
std::mutex mutex_
Mutex for the file watchers.
void unwatch_impl(std::uint64_t key)
void wait_all(watcher::clock_t::duration duration)
std::function< void(const std::vector< entry > &, bool)> notify_callback
auto filetime_to_system_clock(fs::file_time_type ft) -> std::chrono::system_clock::time_point
bool is_any_parent_path(const path &parent, const path &child)
Hash specialization for batch_key to enable use in std::unordered_map.
void append(const observed_changes &rhs)
void append(observed_changes &&rhs)
std::vector< size_t > modified
std::vector< size_t > created
std::vector< watcher::entry > entries