Mads
Multi-Agent Distributed System
Loading...
Searching...
No Matches
watcher.hpp
Go to the documentation of this file.
1#include <atomic>
2#include <chrono>
3#include <filesystem>
4#include <functional>
5#include <future>
6#include <iostream>
7#include <string>
8#include <thread>
9#include <vector>
10
11#include "mads.hpp"
12
13#if defined(__linux__)
14#include <limits.h>
15#include <sys/inotify.h>
16#include <unistd.h>
17#define BUF_LEN (10 * (sizeof(struct inotify_event) + NAME_MAX + 1))
18#elif defined(__APPLE__)
19#include <fcntl.h>
20#include <sys/event.h>
21#include <unistd.h>
22#elif defined(_WIN32)
23#include <windows.h>
24#include <fileapi.h>
25#endif
26
27namespace fs = std::filesystem;
28using namespace std::chrono_literals;
29
30namespace Mads {
31class Watcher {
32public:
33 Watcher(const std::string &file_name, std::chrono::duration<float> to = 0s)
34 : _file_name(file_name), _timeout(to) {
35#if defined(__APPLE__)
36 _fd = open(_file_name.c_str(), O_EVTONLY);
37 _ts.tv_sec =
38 std::chrono::duration_cast<std::chrono::seconds>(_timeout).count();
39 _ts.tv_nsec =
40 std::chrono::duration_cast<std::chrono::nanoseconds>(_timeout).count() %
41 1000000000;
42 EV_SET(&_change, _fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR,
43 NOTE_WRITE, 0, (void *)_file_name.c_str());
44#elif defined(__linux__)
45 _inotify_fd = inotify_init1(IN_NONBLOCK);
46 _watch = inotify_add_watch(_inotify_fd, _file_name.c_str(), IN_MODIFY);
47#elif defined(_WIN32)
48 _to =
49 std::chrono::duration_cast<std::chrono::milliseconds>(_timeout).count();
50 // Extract directory from file path for monitoring
51 fs::path file_path(_file_name);
52 std::string dir_path = file_path.parent_path().string();
53 if (dir_path.empty()) dir_path = ".";
54 _change_handle = FindFirstChangeNotificationA(
55 dir_path.c_str(), FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE);
56 _last_change_time = std::chrono::steady_clock::now() - std::chrono::seconds(1);
57#endif
58 }
59
61#if defined(__APPLE__)
62 close(_fd);
63 close(_kq);
64#elif defined(__linux__)
65 inotify_rm_watch(_inotify_fd, _watch);
66 close(_inotify_fd);
67#elif defined(_WIN32)
68 FindCloseChangeNotification(_change_handle);
69#endif
70 }
71
72
81 void watch(const std::function<void(const std::string &)> &callback) {
82 _watching = true;
83 while (_watching && Mads::Runtime::process_running()) {
84 if (file_modified()) callback(_file_name);
85 std::this_thread::sleep_for(std::chrono::milliseconds(200));
86 }
87 }
88
95 void stop() { _watching = false; }
96
97private:
98 std::string _file_name;
99 std::chrono::duration<float> _timeout;
100 std::atomic<bool> _watching{false};
101#if defined(__linux__)
102 char _buffer[BUF_LEN];
103 int _inotify_fd;
104 int _watch;
105#elif defined(__APPLE__)
106 struct timespec _ts;
107 int _fd;
108 int _kq = kqueue();
109 struct kevent _change;
110 struct kevent _event;
111#elif defined(_WIN32)
112 DWORD _to;
113 HANDLE _change_handle;
114 std::chrono::steady_clock::time_point _last_change_time;
115#endif
116
117 int file_modified() {
118#if defined(__linux__)
119 // Use inotify to monitor file changes on Linux
120 int rc = read(_inotify_fd, _buffer, BUF_LEN);
121 if (rc < 0 && errno == EAGAIN) {
122 std::this_thread::sleep_for(_timeout);
123 return 0;
124 }
125 if (rc < 0 && errno != EAGAIN) {
126 perror("read");
127 }
128 return rc;
129#elif defined(__APPLE__)
130 if (_timeout > 0s) {
131 return kevent(_kq, &_change, 1, &_event, 1, &_ts);
132 } else {
133 // Bounded wait instead of blocking forever, so stop() stays responsive.
134 struct timespec ts{1, 0};
135 return kevent(_kq, &_change, 1, &_event, 1, &ts);
136 }
137#elif defined(_WIN32)
138 // Debounce: only report a change if enough time has passed since the last one
139 auto now = std::chrono::steady_clock::now();
140 // Bounded wait instead of INFINITE, so stop() stays responsive.
141 if (WaitForSingleObject(_change_handle, _to > 0 ? _to : 1000) == WAIT_OBJECT_0) {
142 if (now - _last_change_time > std::chrono::milliseconds(1000)) {
143 _last_change_time = now;
144 FindNextChangeNotification(_change_handle); // Re-arm for next change
145 return 1;
146 } else {
147 FindNextChangeNotification(_change_handle); // Re-arm for next change
148 return 0; // Suppress duplicate
149 }
150 } else {
151 return 0;
152 }
153#endif
154 }
155};
156} // namespace Mads
void watch(const std::function< void(const std::string &)> &callback)
Watch the file, invoking the callback on each modification.
Definition watcher.hpp:81
void stop()
Ask a running watch() loop to return.
Definition watcher.hpp:95
Watcher(const std::string &file_name, std::chrono::duration< float > to=0s)
Definition watcher.hpp:33
Definition agent.hpp:67