I'm currently using the ReadDirectoryChangesW
Function from the Windows API to build a Directory Watcher. The Watcher should monitor a folder for newly added files.
For this i use the following code:
class DirectoryWatcher {
public:
using Callback = std::function<void(const std::string&)>;
DirectoryWatcher(const std::filesystem::path& input_directory, std::vector<std::string> file_types, Callback callback);
explicit operator bool() const {
return is_valid_;
}
bool watch();
void stop();
size_t processedFilesCount() const { return processed_files_count; }
size_t ignoredFilesCount() const { return ignored_files_count; }
private:
bool eventRecv();
bool eventSend();
void handleEvents();
bool hasEvent() const {
return event_buf_len_ready_ != 0;
}
bool isProcessableFile(const std::filesystem::path& path) const;
bool isValidAction(int action) const;
std::filesystem::path input_directory_;
std::vector<std::string> file_types_{};
Callback callback_;
HANDLE path_handle_;
HANDLE completion_token_{ INVALID_HANDLE_VALUE };
unsigned long event_buf_len_ready_{ 0 };
bool is_valid_{ false };
OVERLAPPED event_overlap_{};
std::vector<std::byte> event_buf_{ 64 * 1024 };
size_t processed_files_count{ 0 };
size_t ignored_files_count{ 0 };
};
DirectoryWatcher::DirectoryWatcher(const std::filesystem::path& input_directory, std::vector<std::string> file_types, Callback callback)
: input_directory_{ input_directory },
file_types_{ file_types },
callback_{ std::move(callback) } {
path_handle_ = CreateFileA(input_directory.string().c_str(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr);
if (path_handle_ != INVALID_HANDLE_VALUE) {
completion_token_ = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 0);
}
if (completion_token_ != INVALID_HANDLE_VALUE) {
is_valid_ = CreateIoCompletionPort(path_handle_, completion_token_, (ULONG_PTR)path_handle_, 1);
}
}
bool DirectoryWatcher::watch() {
if (is_valid_) {
eventRecv();
while (is_valid_ && hasEvent()) {
eventSend();
}
while (is_valid_) {
ULONG_PTR completion_key{ 0 };
LPOVERLAPPED overlap{ nullptr };
bool complete = GetQueuedCompletionStatus(completion_token_, &event_buf_len_ready_, &completion_key, &overlap, 16);
if (complete && overlap) {
handleEvents();
}
}
return true;
} else {
return false;
}
}
void DirectoryWatcher::handleEvents() {
while (is_valid_ && hasEvent()) {
eventSend();
eventRecv();
}
}
void DirectoryWatcher::stop() {
is_valid_ = false;
}
bool DirectoryWatcher::eventRecv() {
event_buf_len_ready_ = 0;
DWORD bytes_returned = 0;
memset(&event_overlap_, 0, sizeof(OVERLAPPED));
auto read_ok = ReadDirectoryChangesW(path_handle_, event_buf_.data(), event_buf_.size(), FALSE, FILE_NOTIFY_CHANGE_FILE_NAME, &bytes_returned, &event_overlap_, nullptr);
if (!event_buf_.empty() && read_ok) {
event_buf_len_ready_ = bytes_returned > 0 ? bytes_returned : 0;
return true;
}
if (GetLastError() == ERROR_IO_PENDING) {
event_buf_len_ready_ = 0;
is_valid_ = false;
}
return false;
}
bool DirectoryWatcher::eventSend() {
auto buf = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(event_buf_.data());
if (is_valid_) {
while (buf + sizeof(FILE_NOTIFY_INFORMATION) <= buf + event_buf_len_ready_) {
auto filename = input_directory_ / std::wstring{ buf->FileName, buf->FileNameLength / 2 };
if (isValidAction(buf->Action) && isProcessableFile(filename)) {
callback_({ filename.string() });
processed_files_count++;
} else {
ignored_files_count++;
}
if (buf->NextEntryOffset == 0) {
break;
}
buf = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(reinterpret_cast<std::byte*>(buf) + buf->NextEntryOffset);
}
return true;
} else {
return false;
}
}
bool DirectoryWatcher::isProcessableFile(const std::filesystem::path& path) const {
// Get extension and erase first character (dot-character of extension)
std::string extension = path.extension().string().erase(0, 1);
std::transform(extension.begin(), extension.end(), extension.begin(),
[](unsigned char c) { return std::tolower(c); });
return std::find(file_types_.begin(), file_types_.end(), extension) != file_types_.end();
}
bool DirectoryWatcher::isValidAction(int action) const {
return action == FILE_ACTION_ADDED || action == FILE_ACTION_RENAMED_NEW_NAME;
}
int main() {
int counter{ 0 };
std::cout << "Hello World!\n";
DirectoryWatcher watcher{ "C:\\temp\\dummy", {"dat"}, [&counter](const std::string& filename) {
counter++;
std::cout << "counter: " << counter << '\n';
//processing
std::filesystem::remove(filename);
} };
std::thread th1{ &DirectoryWatcher::watch, &watcher };
std::this_thread::sleep_for(std::chrono::seconds(300));
watcher.stop();
th1.join();
}
I think that i don't have the best way used to do such things so i'm happy for any improvements or suggestions on this. The code should be compilable with c++ 17 and Visual Studio.