* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "file_watcher.h"
#ifdef __APPLE__
#include <dispatch/dispatch.h>
#include <CoreServices/CoreServices.h>
#include <filesystem>
namespace YR {
namespace utility {
struct FileWatcher::Impl {
FSEventStreamRef stream;
dispatch_queue_t queue;
Callback callback;
std::string filename;
std::string watchedPath;
std::atomic<bool> running;
Impl(const std::string& fn, Callback cb)
: stream(nullptr), queue(nullptr), callback(std::move(cb)), filename(fn),
watchedPath(std::filesystem::path(fn).parent_path().string()), running(false) {}
static void FSEventCallback(ConstFSEventStreamRef streamRef,
void* clientCallBackInfo,
size_t numEvents,
void* eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[])
{
auto* impl = static_cast<Impl*>(clientCallBackInfo);
auto targetPath = std::filesystem::path(impl->filename).lexically_normal();
for (size_t i = 0; i < numEvents; ++i) {
if (eventFlags[i] & (kFSEventStreamEventFlagItemModified |
kFSEventStreamEventFlagItemCreated |
kFSEventStreamEventFlagItemRenamed)) {
const char *const *changedPaths = static_cast<const char *const *>(eventPaths);
auto changedPath = std::filesystem::path(changedPaths[i]).lexically_normal();
if (changedPath == targetPath) {
impl->callback(impl->filename);
}
}
}
}
};
FileWatcher::FileWatcher(const std::string &filename, Callback callback)
: impl_(nullptr), filename_(filename), callback_(std::move(callback)), running_(false)
{
}
FileWatcher::~FileWatcher()
{
Stop();
}
void FileWatcher::Start()
{
if (running_) {
return;
}
running_ = true;
impl_ = new Impl(filename_, callback_);
std::string pathToWatch = impl_->watchedPath.empty() ? "." : impl_->watchedPath;
CFStringRef path = CFStringCreateWithCString(nullptr, pathToWatch.c_str(), kCFStringEncodingUTF8);
CFArrayRef pathsToWatch = CFArrayCreate(nullptr, (const void**)&path, 1, nullptr);
FSEventStreamContext context = {0};
context.info = impl_;
impl_->stream = FSEventStreamCreate(nullptr,
Impl::FSEventCallback,
&context,
pathsToWatch,
kFSEventStreamEventIdSinceNow,
1.0,
kFSEventStreamCreateFlagFileEvents);
CFRelease(pathsToWatch);
CFRelease(path);
impl_->queue = dispatch_queue_create("com.yuanrong.fswatcher", nullptr);
FSEventStreamSetDispatchQueue(impl_->stream, impl_->queue);
FSEventStreamStart(impl_->stream);
}
void FileWatcher::Stop()
{
if (!running_) {
return;
}
running_ = false;
if (impl_ && impl_->stream) {
FSEventStreamStop(impl_->stream);
FSEventStreamInvalidate(impl_->stream);
FSEventStreamRelease(impl_->stream);
}
if (impl_ && impl_->queue) {
dispatch_release(impl_->queue);
}
delete impl_;
impl_ = nullptr;
}
void FileWatcher::Watch()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
}
#else
namespace YR {
namespace utility {
FileWatcher::FileWatcher(const std::string &filename, Callback callback)
: fd_(-1), wd_(-1), filename_(filename), callback_(std::move(callback)), running_(false)
{
}
FileWatcher::~FileWatcher()
{
Stop();
}
void FileWatcher::Start()
{
if (running_) {
return;
}
running_ = true;
watcherThread_ = std::thread([this]() {
while (true) {
if (!running_) {
break;
}
Watch();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
});
}
void FileWatcher::Stop()
{
if (!running_) {
return;
}
running_ = false;
if (wd_ >= 0) {
inotify_rm_watch(fd_, wd_);
wd_ = -1;
}
if (fd_ >= 0) {
close(fd_);
fd_ = -1;
}
if (watcherThread_.joinable()) {
watcherThread_.join();
}
}
void FileWatcher::Watch()
{
fd_ = inotify_init1(IN_NONBLOCK);
if (fd_ < 0) {
YRLOG_DEBUG("inotify_init1 failed {}", strerror(errno));
return;
}
wd_ = inotify_add_watch(fd_, filename_.c_str(), WATCH_MASK);
if (wd_ < 0) {
YRLOG_DEBUG("inotify_add_watch failed {}", strerror(errno));
close(fd_);
fd_ = -1;
return;
}
callback_(filename_);
char buffer[EVENT_BUF_LEN];
while (running_) {
int length = read(fd_, buffer, EVENT_BUF_LEN);
if (length < 0) {
if (errno == EINTR) {
continue;
}
if (errno == EAGAIN) {
std::this_thread::sleep_for(std::chrono::seconds(1));
continue;
}
YRLOG_WARN("read error: {}", strerror(errno));
break;
}
int i = 0;
while (i < length) {
struct inotify_event *event = static_cast<struct inotify_event *>(static_cast<void *>(&buffer[i]));
if (event->mask & IN_CLOSE_WRITE) {
callback_(filename_);
}
i += sizeof(struct inotify_event) + event->len;
}
}
}
}
}
#endif