Unravel Engine C++ Reference
Loading...
Searching...
No Matches
watcher.cpp
Go to the documentation of this file.
1#include "watcher.h"
2#include <sstream>
3#include <utility>
5namespace fs
6{
7using namespace std::literals;
8
9namespace
10{
11
12void log_path(const fs::path& /*unused*/)
13{
14}
15
16} // namespace
17
19{
20public:
21
23 {
24 std::vector<watcher::entry> entries;
25
26 std::vector<size_t> created;
27 std::vector<size_t> modified;
28
29 void append(const observed_changes& rhs)
30 {
31
32 for(const auto& e : rhs.entries)
33 {
34 entries.emplace_back(e);
35 }
36
37 auto created_sz_before = created.size();
38 for(auto idx : rhs.created)
39 {
40 created.emplace_back(created_sz_before + idx);
41 }
42
43 auto modified_sz_before = modified.size();
44 for(auto idx : rhs.modified)
45 {
46 modified.emplace_back(modified_sz_before + idx);
47 }
48 }
49
51 {
52
53 for(auto& e : rhs.entries)
54 {
55 entries.emplace_back(std::move(e));
56 }
57
58 auto created_sz_before = created.size();
59 for(auto idx : rhs.created)
60 {
61 created.emplace_back(created_sz_before + idx);
62 }
63
64 auto modified_sz_before = modified.size();
65 for(auto idx : rhs.modified)
66 {
67 modified.emplace_back(modified_sz_before + idx);
68 }
69
70 rhs = {};
71 }
72 };
73 //-----------------------------------------------------------------------------
74 // Name : impl ()
80 //-----------------------------------------------------------------------------
81 impl(const fs::path& path,
82 const pattern_filter& filter,
83 bool recursive,
84 bool initial_list,
85 clock_t::duration poll_interval,
86 notify_callback list_callback)
87 : filter_(filter)
88 , callback_(std::move(list_callback))
89 , poll_interval_(poll_interval)
90 , recursive_(recursive)
91 {
92 root_ = path;
93 observed_changes changes;
94 if(recursive_)
95 {
96 fs::error_code err;
97 for(auto& entry : fs::recursive_directory_iterator(root_, err))
98 {
99 if(filter_.should_include(entry.path()))
100 poll_entry(entry.path(), changes);
101 }
102 }
103 else
104 {
105 fs::error_code err;
106 for(auto& entry : fs::directory_iterator(root_, err))
107 {
108 if(filter_.should_include(entry.path()))
109 poll_entry(entry.path(), changes);
110 }
111 }
112 if(initial_list)
113 {
114 if(!changes.entries.empty() && callback_)
115 {
116 callback_(changes.entries, true);
117 }
118 }
119 }
120
121 void pause()
122 {
123 paused_ = true;
124 }
125
126 void resume()
127 {
128 paused_ = false;
129 }
130
131 //-----------------------------------------------------------------------------
132 // Name : watch ()
138 //-----------------------------------------------------------------------------
139 void watch()
140 {
141 observed_changes changes;
142 bool paused = paused_;
143 if(!paused)
144 {
145 if(!buffered_changes_.entries.empty())
146 {
147 std::swap(changes, buffered_changes_);
148 }
149 }
150 if(recursive_)
151 {
152 fs::error_code err;
153 for(auto& entry : fs::recursive_directory_iterator(root_, err))
154 {
155 if(filter_.should_include(entry.path()))
156 poll_entry(entry.path(), changes);
157 }
158 }
159 else
160 {
161 fs::error_code err;
162 for(auto& entry : fs::directory_iterator(root_, err))
163 {
164 if(filter_.should_include(entry.path()))
165 poll_entry(entry.path(), changes);
166 }
167 }
168 if(paused)
169 {
170 if(!changes.entries.empty())
171 {
172 buffered_changes_.append(std::move(changes));
173 }
174 }
175 else
176 {
178 if(!changes.entries.empty() && callback_)
179 {
180 callback_(changes.entries, false);
181 }
182 }
183 }
184
185 static auto get_original_path(const fs::path& old_path, const fs::path& renamed_path, const fs::path& new_path) -> fs::path
186 {
187 fs::path relative_path = fs::relative(new_path, renamed_path);
188 fs::path original_path = old_path / relative_path;
189 return original_path;
190 }
191
192 static auto check_if_same_extension(const fs::path& p1, const fs::path& p2) -> bool
193 {
194 bool same_extensions = true;
195
196 auto ep = p1;
197 auto fp = p2;
198
199 while(ep.has_extension() || fp.has_extension())
200 {
201 same_extensions &= ep.extension() == fp.extension();
202 ep = ep.stem();
203 fp = fp.stem();
204 }
205
206 return same_extensions;
207 };
208
209 static auto check_if_parent_dir_was_renamed(const std::vector<size_t>& renamed_dirs, const std::vector<watcher::entry>& entries, entry& e) -> bool
210 {
211 //check if parent_dir was renamed
212 for(const auto& renamed_idx : renamed_dirs)
213 {
214 const auto& renamed_e = entries[renamed_idx];
215
216 if(fs::is_any_parent_path(renamed_e.path, e.path))
217 {
219 e.last_path = get_original_path(renamed_e.last_path, renamed_e.path, e.path);
220
221
222 return true;
223 }
224 }
225 return false;
226 };
227
228
229 template<typename Container>
230 static auto check_if_renamed(entry& e, Container& container) -> bool
231 {
232
233 auto it = std::begin(container);
234 while(it != std::end(container))
235 {
236 auto& fi = it->second;
237 fs::error_code err;
238 if(!fs::exists(fi.path, err))
239 {
240
241 if(e.size == fi.size)
242 {
243 auto diff = (e.last_mod_time - fi.last_mod_time);
244 auto d = std::chrono::duration_cast<std::chrono::milliseconds>(diff);
245
246 if(d <= std::chrono::milliseconds(0))
247 {
248 bool same_extensions = check_if_same_extension(e.path, fi.path);
249 if(same_extensions)
250 {
252 e.last_path = fi.path;
253
254 // remove the cached old path entry
255 container.erase(it);
256 return true;
257 }
258 }
259
260 }
261
262 }
263
264 it++;
265 }
266
267 return false;
268
269 };
270
271 template<typename Container>
272 static void check_for_removed(std::vector<watcher::entry>& entries, Container& container)
273 {
274
275 auto it = std::begin(container);
276 while(it != std::end(container))
277 {
278 auto& fi = it->second;
279 fs::error_code err;
280 if(!fs::exists(fi.path, err))
281 {
283 entries.push_back(fi);
284
285 it = container.erase(it);
286 }
287 else
288 {
289 it++;
290 }
291 }
292 }
293
294
295 template<typename Container>
296 static void process_modifications(Container& old_entries,
297 observed_changes& changes)
298 {
299 using namespace std::literals;
300
301
302 std::vector<size_t> renamed_dirs;
303
304 for(auto idx : changes.created)
305 {
306 auto& e = changes.entries[idx];
307
308 //check if parent_dir was renamed
309 if(check_if_parent_dir_was_renamed(renamed_dirs, changes.entries, e))
310 {
311
312 // remove the cached old path entry
313 old_entries.erase(e.last_path.string());
314 continue;
315 }
316
317 // check for rename heuristic
318 if(check_if_renamed(e, old_entries))
319 {
320 if(e.type == fs::file_type::directory)
321 {
322 renamed_dirs.emplace_back(idx);
323 }
324 continue;
325 }
326
327 }
328 check_for_removed(changes.entries, old_entries);
329 }
330
331 //-----------------------------------------------------------------------------
332 // Name : poll_entry ()
338 //-----------------------------------------------------------------------------
339 void poll_entry(const fs::path& path,
340 observed_changes& changes)
341 {
342 // get the last modification time
343 fs::error_code err;
344 auto time = fs::last_write_time(path, err);
345 auto size = fs::file_size(path, err);
346 fs::file_status status = fs::status(path, err);
347 // add a new modification time to the map
348 std::string key = path.string();
349 auto it = entries_.find(key);
350 if(it != entries_.end())
351 {
352 auto& fi = it->second;
353
354 if(fi.last_mod_time != time || fi.size != size || fi.type != status.type())
355 {
356 fi.size = size;
357 fi.last_mod_time = time;
359 fi.type = status.type();
360 changes.entries.push_back(fi);
361 changes.modified.push_back(changes.entries.size() - 1);
362 }
363 else
364 {
366 fi.type = status.type();
367 }
368 }
369 else
370 {
371 // or compare with an older one
372 auto& fi = entries_[key];
373 fi.path = path;
374 fi.last_path = path;
375 fi.last_mod_time = time;
377 fi.size = size;
378 fi.type = status.type();
379
380 changes.entries.push_back(fi);
381 changes.created.push_back(changes.entries.size() - 1);
382 }
383 }
384
385protected:
386 friend class watcher;
387
388
390 fs::path root_;
396 std::map<std::string, watcher::entry> entries_;
398 clock_t::duration poll_interval_ = 500ms;
399
400 clock_t::time_point last_poll_ = clock_t::now();
402 bool recursive_ = false;
403
404 std::atomic<bool> paused_ = {false};
405
407};
408
409static auto get_watcher() -> watcher&
410{
411 // create the static watcher instance
412 static watcher wd;
413 return wd;
414}
415
416auto watcher::watch(const fs::path& path,
417 const pattern_filter& filter,
418 bool recursive,
419 bool initial_list,
420 clock_t::duration poll_interval,
421 notify_callback callback) -> std::uint64_t
422{
423 return watch_impl(path, filter, recursive, initial_list, poll_interval, callback);
424}
425
426void watcher::unwatch(std::uint64_t key)
427{
428 unwatch_impl(key);
429}
430
435
436void watcher::touch(const fs::path& path, bool recursive, fs::file_time_type time)
437{
438 fs::error_code err;
439 if(fs::exists(path, err))
440 {
441 if(fs::is_directory(path, err))
442 {
443 if(recursive)
444 {
445 for(auto& entry : fs::recursive_directory_iterator(path, err))
446 {
447 fs::last_write_time(entry.path(), time, err);
448 }
449 }
450 else
451 {
452 for(auto& entry : fs::directory_iterator(path, err))
453 {
454 fs::last_write_time(entry.path(), time, err);
455 }
456 }
457 // Also update the directory itself
458 fs::last_write_time(path, time, err);
459 }
460 else
461 {
462 fs::last_write_time(path, time, err);
463 }
464 return;
465 }
466 // If not, do nothing (or could log_path(path);)
467 log_path(path);
468}
469
471{
472 close();
473}
474
476{
477 auto& wd = get_watcher();
478
479 {
480 std::lock_guard<std::mutex> lock(wd.mutex_);
481 for(auto& kvp : wd.watchers_)
482 {
483 auto& w = kvp.second;
484 w->pause();
485 }
486 }
487
488
489}
490
492{
493 auto& wd = get_watcher();
494
495 {
496 std::lock_guard<std::mutex> lock(wd.mutex_);
497 for(auto& kvp : wd.watchers_)
498 {
499 auto& w = kvp.second;
500 w->resume();
501 }
502 }
503}
504
505
507{
508 // stop the thread
509 watching_ = false;
510 // remove all watchers
511 unwatch_all();
512
513 if(thread_.joinable())
514 {
515 thread_.join();
516 }
517}
518
520{
521 watching_ = true;
522 thread_ = std::thread(
523 [this]()
524 {
525 platform::set_thread_name("fs::watcher");
526 // keep watching for modifications every ms milliseconds
527 using namespace std::literals;
528 while(watching_)
529 {
530 clock_t::duration sleep_time = 99999h;
531
532 // iterate through each watcher and check for modification
533 std::map<std::uint64_t, std::shared_ptr<impl>> watchers;
534 {
535 std::unique_lock<std::mutex> lock(mutex_);
536 watchers = watchers_;
537 }
538
539 for(auto& pair : watchers)
540 {
541 auto watcher = pair.second;
542
543 auto now = clock_t::now();
544
545 auto diff = (watcher->last_poll_ + watcher->poll_interval_) - now;
546 if(diff <= clock_t::duration(0))
547 {
548 watcher->watch();
549 watcher->last_poll_ = now;
550
551 sleep_time = std::min(sleep_time, watcher->poll_interval_);
552 }
553 else
554 {
555 sleep_time = std::min(sleep_time, diff);
556 }
557 }
558
559 std::unique_lock<std::mutex> lock(mutex_);
560 cv_.wait_for(lock, sleep_time);
561 }
562 });
563}
564
565auto watcher::watch_impl(const fs::path& path,
566 const pattern_filter& filter,
567 bool recursive,
568 bool initial_list,
569 clock_t::duration poll_interval,
570 notify_callback& list_callback) -> std::uint64_t
571{
572 auto& wd = get_watcher();
573 // and start its thread
574 if(!wd.watching_)
575 {
576 wd.start();
577 }
578
579 // add a new watcher
580 if(list_callback)
581 {
582 static std::atomic<std::uint64_t> free_id = {1};
583 auto key = free_id++;
584 {
585 auto imp = std::make_shared<impl>(path, filter, recursive, initial_list, poll_interval, std::move(list_callback));
586 std::lock_guard<std::mutex> lock(wd.mutex_);
587 wd.watchers_.emplace(key, std::move(imp));
588 }
589 wd.cv_.notify_all();
590 return key;
591 }
592
593 return 0;
594}
595
596void watcher::unwatch_impl(std::uint64_t key)
597{
598 auto& wd = get_watcher();
599
600 {
601 std::lock_guard<std::mutex> lock(wd.mutex_);
602 wd.watchers_.erase(key);
603 }
604 wd.cv_.notify_all();
605}
606
608{
609 auto& wd = get_watcher();
610 {
611 std::lock_guard<std::mutex> lock(wd.mutex_);
612 wd.watchers_.clear();
613 }
614 wd.cv_.notify_all();
615}
616
617auto to_string(const watcher::entry& e) -> std::string
618{
619 static auto file_type_to_string = [](file_type type) -> std::string
620 {
621 switch(type)
622 {
623 case file_type::regular:
624 return "file";
625 case file_type::directory:
626 return "directory";
627 default:
628 return "other";
629 }
630 };
631
632 static auto status_to_string = [](watcher::entry_status status) -> std::string
633 {
634 switch(status)
635 {
637 return "created";
639 return "modified";
641 return "removed";
643 return "renamed";
644 default:
645 return "unmodified";
646 }
647 };
648
649 std::stringstream ss;
650 ss << "{\"" << int64_t(e.last_mod_time.time_since_epoch().count()) << "\":[" << e.path << "," << file_type_to_string(e.type)
651 << "," << status_to_string(e.status) << "]}";
652 return ss.str();
653}
654} // namespace fs
manifold_type type
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...
pattern_filter filter_
Filter applied.
Definition watcher.cpp:392
clock_t::duration poll_interval_
Definition watcher.cpp:398
clock_t::time_point last_poll_
Definition watcher.cpp:400
static auto check_if_same_extension(const fs::path &p1, const fs::path &p2) -> bool
Definition watcher.cpp:192
static auto check_if_parent_dir_was_renamed(const std::vector< size_t > &renamed_dirs, const std::vector< watcher::entry > &entries, entry &e) -> bool
Definition watcher.cpp:209
fs::path root_
Path to watch.
Definition watcher.cpp:390
notify_callback callback_
Callback for list of modifications.
Definition watcher.cpp:394
static void check_for_removed(std::vector< watcher::entry > &entries, Container &container)
Definition watcher.cpp:272
static auto check_if_renamed(entry &e, Container &container) -> bool
Definition watcher.cpp:230
std::atomic< bool > paused_
Definition watcher.cpp:404
std::map< std::string, watcher::entry > entries_
Cache watched files.
Definition watcher.cpp:396
impl(const fs::path &path, const pattern_filter &filter, bool recursive, bool initial_list, clock_t::duration poll_interval, notify_callback list_callback)
Definition watcher.cpp:81
observed_changes buffered_changes_
Definition watcher.cpp:406
void poll_entry(const fs::path &path, observed_changes &changes)
Definition watcher.cpp:339
static auto get_original_path(const fs::path &old_path, const fs::path &renamed_path, const fs::path &new_path) -> fs::path
Definition watcher.cpp:185
static void process_modifications(Container &old_entries, observed_changes &changes)
Definition watcher.cpp:296
static void unwatch_impl(std::uint64_t key)
Definition watcher.cpp:596
static void unwatch(std::uint64_t key)
Un-watches a previously registered file or directory.
Definition watcher.cpp:426
static void resume()
Definition watcher.cpp:491
static void unwatch_all_impl()
Definition watcher.cpp:607
std::function< void(const std::vector< entry > &, bool)> notify_callback
Definition watcher.h:42
static void pause()
Definition watcher.cpp:475
std::atomic< bool > watching_
Atomic bool sync.
Definition watcher.h:150
static void unwatch_all()
Un-watches all previously registered file or directory.
Definition watcher.cpp:431
static void touch(const fs::path &path, bool recursive, fs::file_time_type time=fs::now())
Sets the last modification time of a file or directory. by default sets the time to the current time.
Definition watcher.cpp:436
void start()
Definition watcher.cpp:519
std::condition_variable cv_
Definition watcher.h:152
std::map< std::uint64_t, std::shared_ptr< impl > > watchers_
Definition watcher.h:157
static auto watch_impl(const fs::path &path, const pattern_filter &filter, bool recursive, bool initial_list, clock_t::duration poll_interval, notify_callback &list_callback) -> std::uint64_t
Definition watcher.cpp:565
void close()
Definition watcher.cpp:506
std::thread thread_
Thread that polls for changes.
Definition watcher.h:154
std::mutex mutex_
Mutex for the file watchers.
Definition watcher.h:148
static auto watch(const fs::path &path, const pattern_filter &filter, bool recursive, bool initial_list, clock_t::duration poll_interval, notify_callback callback) -> std::uint64_t
Watches a file or directory for modification and call back the specified std::function....
Definition watcher.cpp:416
Definition cache.hpp:11
bool is_any_parent_path(const path &parent, const path &child)
auto to_string(const watcher::entry &e) -> std::string
Definition watcher.cpp:617
void set_thread_name(const char *threadName)
Definition thread.hpp:83
void append(const observed_changes &rhs)
Definition watcher.cpp:29
std::vector< size_t > created
Definition watcher.cpp:26
std::vector< size_t > modified
Definition watcher.cpp:27
std::vector< watcher::entry > entries
Definition watcher.cpp:24
void append(observed_changes &&rhs)
Definition watcher.cpp:50