Given a path, a file watcher is routinely checking its availability (in case of creation/deletion) or timestamp (in case the file exists and its contents have been modified). The code below (take from here) is work in progress and I am adapting it to also support single file (the original code supports multiple where the given path is a directory).
class FileWatcher {
public:
std::string m_path;
std::chrono::duration<int, std::milli> m_delay;
std::unique_ptr<std::thread> m_watcher_thread;
FileWatcher(std::string path, std::chrono::duration<int, std::milli> delay);
void start(const std::function<void(std::string, FileStatus)>& action);
private:
std::unordered_map<std::string, std::filesystem::file_time_type> m_paths;
bool m_running = true;
bool contains(const std::string& key);
};
FileWatcher::FileWatcher(std::string path, std::chrono::duration<int, std::milli> delay) : m_path{ path }, m_delay{ delay } {
// std::this_thread::sleep_for(delay);
auto const p = std::filesystem::path(m_path);
if (std::filesystem::is_regular_file(p))
{
m_paths[p.string()] = std::filesystem::last_write_time(p);
}
else if (std::filesystem::is_directory(p))
{
for (auto const& file : std::filesystem::recursive_directory_iterator(m_path)) {
m_paths[file.path().string()] = std::filesystem::last_write_time(file);
}
}
else
{
throw std::exception("Given path to watch is expected to be a regular file or directory");
}
}
bool FileWatcher::contains(const std::string& key)
{
auto el = m_paths.find(key);
return el != m_paths.end();
}
void FileWatcher::start(const std::function<void(std::string, FileStatus)>& action) {
m_watcher_thread = std::unique_ptr<std::thread>(new std::thread(&action));
while (m_running) {
... // WIP Check status of file and call action
}
}
I initialize my watcher as follows:
config_watcher = std::shared_ptr<FileWatcher>(new FileWatcher{ "./config.json" , std::chrono::milliseconds(5000) });
and start it by calling (logger_spd
is an std::shared_ptr
to spdlog logger using a file sink)
config_watcher->start([](std::string path, FileStatus status) -> void {
// Process only regular files, all other file types are ignored
if (!std::filesystem::is_regular_file(std::filesystem::path(path)) && status != FileStatus::erased) {
return;
}
switch (status) {
case FileStatus::created:
{
std::lock_guard<std::mutex> guard(config_update_mutex);
logger_spd->info("Configuration created");
break;
}
case FileStatus::modified:
{
std::lock_guard<std::mutex> guard(config_update_mutex);
logger_spd->info("Configuration modified");
break;
}
case FileStatus::erased:
{
std::lock_guard<std::mutex> guard(config_update_mutex);
logger_spd->info("Configuration deleted");
break;
}
default:
std::lock_guard<std::mutex> guard(config_update_mutex);
logger_spd->error("Unknown file status");
}
std::this_thread::sleep_for(config_watcher->m_delay);
}
);
The initial code is sub-optimal as mentioned in at least on of the comments below the article I have used as a source because of, among other issues, the std::this_thread::sleep_for()
call inside the FileWatcher::start()
that blocks the rest of the program unless instantiated in a separate thread. That is why I am trying to modify the code to act more as a typical worker thread, meaning that I would like to pass the action
function (argument of FileWatcher::start()
) to an underlying thread constructor and then manage everything inside the class.
I am having a problem to wrap my head around the forwarding the lambda to the underlying thread's constructor through the FileWatcher::start()
function.
You should not start the thread with the action
, instead you should have a different function that contains the loop and which calls the action
inside the loop and use that function for the thread.
Perhaps something like this:
void FileWatcher::start(const std::function<void(std::string, FileStatus)>& action) {
m_watcher_thread = std::thread([action]() {
while (m_running) {
// Wait until something happens...
action(file, status);
}
});
}
Note that I don't use a pointer for the thread object, as that's not needed.
The "sleep" should also be inside the loop, and not in the action
function.