44 file_watchers_[0] = std::make_unique<::wtr::watch>(path, [
this, watcher_idx = 0](const ::wtr::event&
event) ->
void
46 handle_raw_event(
event, watcher_idx);
52 processing_thread_func();
60 std::lock_guard<std::mutex> lock(
mutex_);
94 void advance_watcher()
102 current_watcher->close();
103 current_watcher.reset();
113 file_watchers_[next_index] = std::make_unique<::wtr::watch>(
root_, [
this, watcher_idx = next_index](const ::wtr::event&
event) ->
void
115 handle_raw_event(
event, watcher_idx);
123 void handle_raw_event(const ::wtr::event&
event,
int watcher_idx)
132 if(
event.path_type == ::wtr::event::path_type::watcher)
138 if(
event.effect_type != ::wtr::event::effect_type::create &&
139 event.effect_type != ::wtr::event::effect_type::modify &&
140 event.effect_type != ::wtr::event::effect_type::destroy &&
141 event.effect_type != ::wtr::event::effect_type::rename)
146 std::lock_guard<std::mutex> lock(
mutex_);
155 void processing_thread_func()
159 std::unique_lock<std::mutex> lock(
mutex_);
164 auto timeout = std::chrono::milliseconds(250);
169 auto now = std::chrono::steady_clock::now();
171 auto diff = timeout - time_since_last_event;
173 while(diff > std::chrono::milliseconds(0))
176 std::this_thread::sleep_for(diff);
179 now = std::chrono::steady_clock::now();
183 diff = std::chrono::milliseconds(0);
188 if(time_since_last_event > timeout)
190 diff = std::chrono::milliseconds(0);
193 diff = timeout - time_since_last_event;
199 process_events_unlocked();
206 process_events_unlocked();
210 void process_events_unlocked()
217 bool should_advance_watcher =
false;
221 if(
event.effect_type == ::wtr::event::effect_type::rename &&
223 event.path_type == ::wtr::event::path_type::dir)
226 should_advance_watcher =
true;
231 std::vector<::wtr::event> flattened_events = flatten_raw_events(
pending_events_);
239 if(should_advance_watcher)
244 if(!flattened_events.empty())
246 std::cout <<
"--------------------------------" << std::endl;
248 std::cout <<
"Emitting " << flattened_events.size() <<
" raw events" << std::endl;
249 for(
const auto&
event : flattened_events)
251 std::cout <<
"Event: " <<
event << std::endl;
253 std::cout <<
"--------------------------------" << std::endl;
263 static auto flatten_raw_events(
const std::vector<::wtr::event>& raw_events) -> std::vector<::wtr::event>
266 std::set<fs::path> renamed_old_paths;
267 std::vector<::wtr::event> rename_events;
269 for(
const auto&
event : raw_events)
271 if(
event.effect_type == ::wtr::event::effect_type::rename)
273 renamed_old_paths.insert(
event.path_name);
274 rename_events.push_back(
event);
279 std::map<fs::path, std::vector<::wtr::event>> path_to_events;
281 for(
const auto&
event : raw_events)
284 if(
event.effect_type == ::wtr::event::effect_type::rename)
290 if(
event.effect_type == ::wtr::event::effect_type::destroy && renamed_old_paths.contains(
event.path_name))
295 path_to_events[
event.path_name].push_back(
event);
298 std::vector<::wtr::event> flattened;
301 for(
auto& [path, events] : path_to_events)
304 std::sort(events.begin(), events.end(),
305 [](const ::wtr::event&
a, const ::wtr::event&
b) ->
bool
307 return a.effect_time < b.effect_time;
311 bool has_create =
false;
312 bool has_modify =
false;
313 bool has_destroy =
false;
314 ::wtr::event last_modify;
315 ::wtr::event last_destroy;
317 for(
const auto&
event : events)
319 if(
event.effect_type == ::wtr::event::effect_type::create)
323 else if(
event.effect_type == ::wtr::event::effect_type::modify)
328 else if(
event.effect_type == ::wtr::event::effect_type::destroy)
331 last_destroy =
event;
336 if(has_create && has_modify)
339 for(
auto&
event : events)
341 if(
event.effect_type == ::wtr::event::effect_type::create)
344 event.effect_time = last_modify.effect_time;
345 flattened.push_back(
event);
351 else if(has_modify && !has_create)
353 flattened.push_back(last_modify);
358 flattened.push_back(last_destroy);
363 for(
const auto&
event : events)
365 if(
event.effect_type == ::wtr::event::effect_type::create)
367 flattened.push_back(
event);
375 flattened.insert(flattened.end(), rename_events.begin(), rename_events.end());
386 std::lock_guard<std::mutex> lock(
mutex_);
387 process_events_unlocked();
419 watcher::clock_t::duration poll_interval,
421 std::shared_ptr<directory_listener> listener,
422 const std::string& watcher_name)
425 , recursive_(recursive)
426 , callback_(
std::move(callback))
427 , listener_(
std::move(listener))
428 , init_time_(
watcher::clock_t::now())
429 , init_time_timestamp_(
std::chrono::system_clock::now())
430 , watcher_name_(watcher_name)
433 initialize_entries(initial_list);
436 slot_key_ = listener_->on_raw_events.connect([
this](
const std::vector<::wtr::event>& raw_events) ->
void
438 handle_raw_events(raw_events);
446 listener_->on_raw_events.disconnect(slot_key_);
459 if(!buffered_changes_.empty())
461 std::vector<watcher::entry> changes_to_process;
462 std::swap(changes_to_process, buffered_changes_);
464 if(!changes_to_process.empty() && callback_)
466 callback_(changes_to_process,
false);
487 void initialize_entries(
bool emit_initial_list)
491 std::vector<watcher::entry> initial_entries;
495 for(
auto&
entry : fs::recursive_directory_iterator(path_, err))
500 fs::file_status file_status = fs::status(
entry.path(), err2);
501 auto file_type = file_status.type();
502 if(filter_passed || (file_type == fs::file_type::directory))
505 e.path =
entry.path();
506 e.last_path =
entry.path();
510 e.last_mod_time = fs::last_write_time(
entry.path(), err3);
511 e.size = fs::file_size(
entry.path(), err3);
515 std::string key = e.path.string();
518 if(emit_initial_list && filter_passed)
520 initial_entries.push_back(e);
527 for(
auto&
entry : fs::directory_iterator(path_, err))
531 fs::file_status file_status = fs::status(
entry.path(), err2);
532 auto file_type = file_status.type();
533 if(filter_passed || (file_type == fs::file_type::directory))
537 e.last_path =
entry.path();
541 e.last_mod_time = fs::last_write_time(
entry.path(), err3);
542 e.size = fs::file_size(
entry.path(), err3);
546 std::string
key =
e.path.string();
549 if(emit_initial_list && filter_passed)
551 initial_entries.push_back(e);
558 if(emit_initial_list && !initial_entries.empty() && callback_)
560 callback_(initial_entries,
true);
564 void handle_raw_events(
const std::vector<::wtr::event>& raw_events)
567 observed_changes changes = process_and_filter_events(raw_events);
569 if(changes.entries.empty())
578 buffered_changes_.insert(buffered_changes_.end(), changes.entries.begin(), changes.entries.end());
585 callback_(changes.entries,
false);
589 auto is_path_under_watch(
const fs::path& event_path)
const ->
bool
596 auto canonical_event_path = fs::weakly_canonical(event_path, ec);
597 auto canonical_watch_path = fs::weakly_canonical(path_, ec);
600 auto rel = canonical_event_path.lexically_relative(canonical_watch_path);
601 return !(rel.empty() || rel.string().substr(0, 2) ==
"..");
605 auto get_system_timestamp(const ::wtr::event&
event) -> std::chrono::system_clock::time_point
609 if(
event.effect_type == ::wtr::event::effect_type::modify ||
event.effect_type == ::wtr::event::effect_type::create)
611 std::chrono::system_clock::time_point system_timestamp;
613 auto file_timestamp = fs::last_write_time(
event.path_name, err);
620 system_timestamp = std::chrono::system_clock::now();
623 return system_timestamp;
625 auto effect_time = std::chrono::nanoseconds(
event.effect_time);
626 auto system_timestamp = std::chrono::system_clock::time_point(std::chrono::duration_cast<std::chrono::system_clock::duration>(effect_time));
627 return system_timestamp;
630 auto get_file_type(const ::wtr::event&
event) -> fs::file_type
632 switch(
event.path_type)
634 case ::wtr::event::path_type::dir:
636 return fs::file_type::directory;
638 case ::wtr::event::path_type::file:
640 return fs::file_type::regular;
642 case ::wtr::event::path_type::hard_link:
644 return fs::file_type::symlink;
646 case ::wtr::event::path_type::sym_link:
648 return fs::file_type::symlink;
652 return fs::file_type::not_found;
655 return fs::file_type::not_found;
658 auto process_and_filter_events(
const std::vector<::wtr::event>& raw_events) -> observed_changes
660 observed_changes changes;
662 if(!watcher_name_.empty())
664 std::cout <<
"--------------------------------" << std::endl;
667 if(entries_.size() > 1)
675 for(
const auto&
event : raw_events)
680 if(
event.effect_type == ::wtr::event::effect_type::rename &&
event.associated)
682 e.path =
event.associated->path_name;
683 e.last_path =
event.path_name;
685 e.type = get_file_type(
event);
687 if(
e.type == fs::file_type::not_found)
692 auto system_timestamp = get_system_timestamp(
event);
694 if(system_timestamp < init_time_timestamp_)
701 if(fs::exists(
e.path, err))
703 e.last_mod_time = fs::last_write_time(
e.path, err);
704 e.size = fs::file_size(
e.path, err);
706 else if(fs::exists(
e.last_path, err))
708 e.last_mod_time = fs::last_write_time(
e.path, err);
709 e.size = fs::file_size(
e.path, err);
714 if(
e.type == fs::file_type::regular)
717 if(!is_path_under_watch(
e.path) && !is_path_under_watch(
e.last_path))
727 else if(
e.type == fs::file_type::directory)
732 auto canonical_old_path = fs::weakly_canonical(
e.last_path, ec);
733 auto canonical_watch_path = fs::weakly_canonical(path_, ec);
735 if(canonical_old_path == canonical_watch_path)
746 fs::path relative_path = canonical_watch_path.lexically_relative(canonical_old_path);
747 path_ =
e.path / relative_path;
754 if(entries_.contains(
e.last_path.string()))
759 std::string old_key =
e.last_path.string();
760 std::string new_key =
e.path.string();
761 entries_.erase(old_key);
762 entries_[new_key] =
e;
764 changes.entries.push_back(e);
766 else if(
e.type == fs::file_type::directory)
771 changes.entries.push_back(e);
777 e.last_path =
e.path;
779 std::string new_key =
e.path.string();
780 entries_[new_key] =
e;
782 changes.entries.push_back(e);
783 changes.created.push_back(changes.entries.size() - 1);
790 e.path =
event.path_name;
791 e.last_path =
event.path_name;
792 e.type = get_file_type(
event);
794 if(
e.type == fs::file_type::not_found)
800 if(!is_path_under_watch(
e.path))
812 auto system_timestamp = get_system_timestamp(
event);
814 if(system_timestamp < init_time_timestamp_)
820 switch(
event.effect_type)
822 case ::wtr::event::effect_type::create:
824 std::string
key =
e.path.string();
828 if(fs::exists(
e.path, err))
830 e.last_mod_time = fs::last_write_time(
e.path, err);
831 e.size = fs::file_size(
e.path, err);
834 changes.entries.push_back(e);
835 changes.created.push_back(changes.entries.size() - 1);
842 case ::wtr::event::effect_type::destroy:
845 std::string
key =
e.path.string();
846 auto it = entries_.find(key);
847 if(it != entries_.end())
849 e.last_mod_time = it->second.last_mod_time;
850 e.size = it->second.size;
856 changes.entries.push_back(e);
861 case ::wtr::event::effect_type::modify:
864 std::string
key =
e.path.string();
866 if(fs::exists(
e.path, err))
868 e.last_mod_time = fs::last_write_time(
e.path, err);
869 e.size = fs::file_size(
e.path, err);
872 changes.entries.push_back(e);
873 changes.modified.push_back(changes.entries.size() - 1);
887 this->process_modifications(entries_, changes);
888 if(changes.entries.size() > 0)
890 std::cout <<
"--------------------------------" << std::endl;
891 std::cout <<
"Watcher : " << watcher_name_ << std::endl;
892 std::cout <<
"Path: " << path_.string() << std::endl;
893 std::cout <<
"Recursive: " << recursive_ << std::endl;
894 std::cout <<
"Filter Exclude Patterns: ";
897 std::cout << pattern.get_pattern() <<
" ";
899 std::cout << std::endl;
900 std::cout <<
"Filter Include Patterns: ";
903 std::cout << pattern.get_pattern() <<
" ";
905 std::cout << std::endl;
906 std::cout <<
"Changes: " << changes.entries.size() << std::endl;
907 for(
const auto&
entry : changes.entries)
911 std::cout <<
"--------------------------------" << std::endl;
918 static auto get_original_path(
const fs::path& old_path,
const fs::path& renamed_path,
const fs::path& new_path) -> fs::path
920 fs::path relative_path = fs::relative(new_path, renamed_path);
921 fs::path original_path = old_path / relative_path;
923 return fs::weakly_canonical(original_path, err);
926 static auto check_if_same_extension(
const fs::path& p1,
const fs::path& p2) ->
bool
928 bool same_extensions =
true;
933 while(ep.has_extension() || fp.has_extension())
935 same_extensions &= ep.extension() == fp.extension();
940 return same_extensions;
943 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
946 for(
const auto& renamed_idx : renamed_dirs)
948 const auto& renamed_e =
entries[renamed_idx];
953 e.last_path = get_original_path(renamed_e.last_path, renamed_e.path,
e.path);
961 template<
typename Container>
962 static auto check_if_renamed(watcher::entry& e, Container& container) ->
bool
964 auto it = std::begin(container);
965 while(it != std::end(container))
967 auto& fi = it->second;
969 if(!fs::exists(fi.path, err))
971 if(
e.size == fi.size)
973 auto diff = (
e.last_mod_time - fi.last_mod_time);
974 auto d = std::chrono::duration_cast<std::chrono::milliseconds>(diff);
976 if(d <= std::chrono::milliseconds(10))
978 bool same_extensions = check_if_same_extension(
e.path, fi.path);
981 if(d <= std::chrono::milliseconds(0))
984 e.last_path = fi.path;
991 std::cout <<
"Same file modification time difference: " << std::endl;
994 std::cout <<
"Difference: " <<
d.count() <<
" milliseconds" << std::endl;
995 std::cout <<
"--------------------------------" << std::endl;
1007 template<
typename Container>
1008 static void prune_removed_entries(Container& container)
1011 auto it = std::begin(container);
1012 while(it != std::end(container))
1014 auto& fi = it->second;
1019 it = container.erase(it);
1028 template<
typename Container>
1029 void process_modifications(Container& old_entries, observed_changes& changes)
1031 if(changes.entries.empty())
1036 std::vector<size_t> renamed_dirs;
1039 for(
auto idx : changes.created)
1041 auto&
e = changes.entries[idx];
1044 if(check_if_parent_dir_was_renamed(renamed_dirs, changes.entries, e))
1047 old_entries.erase(
e.last_path.string());
1052 if(check_if_renamed(e, old_entries))
1054 if(
e.type == fs::file_type::directory)
1056 renamed_dirs.emplace_back(idx);
1063 for(
const auto&
entry : changes.entries)
1072 std::vector<std::pair<std::string, watcher::entry>> children_to_update;
1074 for(
auto& [key, cached_entry] : old_entries)
1083 watcher::entry updated_entry = cached_entry;
1084 updated_entry.last_path = cached_entry.path;
1085 updated_entry.path = get_original_path(
entry.path,
entry.last_path, cached_entry.path);
1089 children_to_update.push_back({
key, updated_entry});
1094 for(
const auto& [old_key, updated_entry] : children_to_update)
1096 old_entries.erase(old_key);
1097 std::string new_key = updated_entry.path.string();
1098 old_entries[new_key] = updated_entry;
1101 changes.entries.push_back(updated_entry);
1105 prune_removed_entries(old_entries);
1109 pattern_filter filter_;
1112 std::shared_ptr<directory_listener> listener_;
1114 std::chrono::steady_clock::time_point init_time_;
1115 std::chrono::system_clock::time_point init_time_timestamp_;
1116 uint64_t slot_key_ = 0;
1117 std::atomic<bool> paused_ =
false;
1118 std::vector<watcher::entry> buffered_changes_;
1120 std::map<std::string, watcher::entry> entries_;
1121 std::string watcher_name_;