Skip to content

Commit 9b3cced

Browse files
committed
feat: add FileWatcher implementation
1 parent 10de605 commit 9b3cced

File tree

8 files changed

+641
-0
lines changed

8 files changed

+641
-0
lines changed

engine/foundation/core/core_files.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ set(FILES
7373
private/io/file_impl.cpp
7474
private/io/file_reader.cpp
7575
private/io/file_stream.cpp
76+
private/io/file_watcher.cpp
77+
private/io/file_watcher_impl.cpp
7678
private/io/file_writer.cpp
7779
private/io/filesystem.cpp
7880
private/io/filesystem_helper.cpp

engine/foundation/core/core_linux_files.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ set(FILES
2727
private/linux/linux_display_device.cpp
2828
private/linux/linux_event_device.cpp
2929
private/linux/linux_file.cpp
30+
private/linux/linux_file_watcher.cpp
3031
private/linux/linux_filesystem.cpp
3132
private/linux/linux_keyboard_device.cpp
3233
private/linux/linux_library.cpp
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
////////////////////////////////////////////////////////////////////////////////////////////////////
2+
// Copyright (c) 2025 RacoonStudios
3+
//
4+
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
5+
// software and associated documentation files (the "Software"), to deal in the Software
6+
// without restriction, including without limitation the rights to use, copy, modify, merge,
7+
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
8+
// to whom the Software is furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in all copies or
11+
// substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
15+
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
16+
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
17+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18+
// DEALINGS IN THE SOFTWARE.
19+
////////////////////////////////////////////////////////////////////////////////////////////////////
20+
21+
22+
//[-------------------------------------------------------]
23+
//[ Includes ]
24+
//[-------------------------------------------------------]
25+
#include "core/io/file_watcher.h"
26+
#include "core/memory/memory.h"
27+
28+
#if defined(LINUX)
29+
#include "core/linux/linux_file_watcher.h"
30+
#endif
31+
32+
33+
//[-------------------------------------------------------]
34+
//[ Namespace ]
35+
//[-------------------------------------------------------]
36+
namespace core {
37+
38+
39+
//[-------------------------------------------------------]
40+
//[ Classes ]
41+
//[-------------------------------------------------------]
42+
FileWatcher::FileWatcher(const Path &path)
43+
: mImpl(nullptr) {
44+
#if defined(LINUX)
45+
mImpl = re_new<LinuxFileWatcher>(path);
46+
#endif
47+
}
48+
49+
FileWatcher::FileWatcher(const String &watcherPath)
50+
: mImpl(nullptr) {
51+
#if defined(LINUX)
52+
mImpl = re_new<LinuxFileWatcher>(watcherPath);
53+
#endif
54+
}
55+
56+
FileWatcher::~FileWatcher() {
57+
re_delete(mImpl);
58+
}
59+
60+
bool FileWatcher::any_changes() const {
61+
return mImpl->any_changes();
62+
}
63+
64+
void FileWatcher::close() {
65+
mImpl->close();
66+
}
67+
68+
69+
//[-------------------------------------------------------]
70+
//[ Namespace ]
71+
//[-------------------------------------------------------]
72+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
////////////////////////////////////////////////////////////////////////////////////////////////////
2+
// Copyright (c) 2025 RacoonStudios
3+
//
4+
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
5+
// software and associated documentation files (the "Software"), to deal in the Software
6+
// without restriction, including without limitation the rights to use, copy, modify, merge,
7+
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
8+
// to whom the Software is furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in all copies or
11+
// substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
15+
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
16+
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
17+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18+
// DEALINGS IN THE SOFTWARE.
19+
////////////////////////////////////////////////////////////////////////////////////////////////////
20+
21+
22+
//[-------------------------------------------------------]
23+
//[ Includes ]
24+
//[-------------------------------------------------------]
25+
#include "core/io/file_watcher_impl.h"
26+
27+
28+
//[-------------------------------------------------------]
29+
//[ Namespace ]
30+
//[-------------------------------------------------------]
31+
namespace core {
32+
33+
34+
//[-------------------------------------------------------]
35+
//[ Classes ]
36+
//[-------------------------------------------------------]
37+
FileWatcherImpl::FileWatcherImpl(const Path &path)
38+
: mPath(path) {
39+
40+
}
41+
42+
FileWatcherImpl::FileWatcherImpl(const String &watcherPath)
43+
: mPath(watcherPath) {
44+
45+
}
46+
47+
FileWatcherImpl::~FileWatcherImpl() {
48+
49+
}
50+
51+
52+
//[-------------------------------------------------------]
53+
//[ Namespace ]
54+
//[-------------------------------------------------------]
55+
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
////////////////////////////////////////////////////////////////////////////////////////////////////
2+
// Copyright (c) 2025 RacoonStudios
3+
//
4+
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
5+
// software and associated documentation files (the "Software"), to deal in the Software
6+
// without restriction, including without limitation the rights to use, copy, modify, merge,
7+
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
8+
// to whom the Software is furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in all copies or
11+
// substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
15+
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
16+
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
17+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18+
// DEALINGS IN THE SOFTWARE.
19+
////////////////////////////////////////////////////////////////////////////////////////////////////
20+
21+
22+
//[-------------------------------------------------------]
23+
//[ Includes ]
24+
//[-------------------------------------------------------]
25+
#include "core/linux/linux_file_watcher.h"
26+
#include "core/memory/memory.h"
27+
#include <dirent.h>
28+
#include <unistd.h>
29+
#include <sys/inotify.h>
30+
31+
32+
//[-------------------------------------------------------]
33+
//[ Namespace ]
34+
//[-------------------------------------------------------]
35+
namespace core {
36+
37+
38+
namespace detail {
39+
40+
static core::Vector<core::String> list_directory_entries(const char* dirPath) {
41+
core::Vector<core::String> entries;
42+
43+
DIR* dir = opendir(dirPath);
44+
45+
if (dir != nullptr) {
46+
while (true) {
47+
struct dirent* dirEntry = readdir(dir);
48+
49+
if (dirEntry == nullptr) {
50+
break;
51+
}
52+
53+
core::uint32 numBytes = strlen(dirEntry->d_name);
54+
char* stringBuffer = new char[numBytes];
55+
56+
core::Memory::copy(&stringBuffer[0], dirEntry->d_name, numBytes + 1);
57+
58+
entries.push_back(stringBuffer);
59+
}
60+
}
61+
62+
return entries;
63+
}
64+
65+
static void create_watcher_recursively(core::int32 fileHandle, const char* dirPath) {
66+
core::Path path(dirPath);
67+
if (path.is_directory()) {
68+
// Entry for a directory, add a watcher for that as well
69+
inotify_add_watch(
70+
fileHandle,
71+
dirPath,
72+
IN_CREATE | IN_DELETE | IN_MODIFY | IN_MOVED_TO | IN_MOVED_FROM | IN_MOVE_SELF | IN_DELETE_SELF);
73+
74+
// Get all elements of the directory and call this recursively
75+
core::Vector<core::String> fileEntries = list_directory_entries(dirPath);
76+
77+
for (core::uint32 i = 0; i < fileEntries.size(); ++i) {
78+
core::String& fileEntry = fileEntries[i];
79+
// Skip the entries for the current directory and the parent directory
80+
if (fileEntry[0] == '.') {
81+
continue;
82+
}
83+
84+
core::Path fullFilePath = path / fileEntry;
85+
86+
// Call recursively
87+
create_watcher_recursively(
88+
fileHandle,
89+
fullFilePath.get_native_path().c_str());
90+
}
91+
}
92+
}
93+
94+
}
95+
96+
97+
//[-------------------------------------------------------]
98+
//[ Classes ]
99+
//[-------------------------------------------------------]
100+
LinuxFileWatcher::LinuxFileWatcher(const Path &path)
101+
: FileWatcherImpl(path)
102+
, mHandle(0) {
103+
// Initialize the notification handle
104+
initialize_notification_handle();
105+
}
106+
107+
LinuxFileWatcher::LinuxFileWatcher(const String &watcherPath)
108+
: FileWatcherImpl(watcherPath)
109+
, mHandle(0) {
110+
// Initialize the notification handle
111+
initialize_notification_handle();
112+
}
113+
114+
LinuxFileWatcher::~LinuxFileWatcher() {
115+
// Nothing to do here
116+
close();
117+
}
118+
119+
bool LinuxFileWatcher::any_changes() const {
120+
fd_set fileHandles;
121+
FD_ZERO(&fileHandles);
122+
FD_SET(mHandle, &fileHandles);
123+
124+
struct timeval timeout;
125+
timeout.tv_sec = 0;
126+
timeout.tv_usec = 0;
127+
128+
bool anyChanges = select(FD_SETSIZE, &fileHandles, nullptr, nullptr, &timeout) > 0;
129+
130+
// Read flush element
131+
if (anyChanges) {
132+
read(mHandle, nullptr, 1024);
133+
}
134+
135+
return anyChanges;
136+
}
137+
138+
void LinuxFileWatcher::close() {
139+
::close(mHandle);
140+
}
141+
142+
143+
void LinuxFileWatcher::initialize_notification_handle() {
144+
mHandle = inotify_init();
145+
146+
// Add watcher
147+
if (mPath.is_directory()) {
148+
// Add watcher recursively here
149+
detail::create_watcher_recursively(
150+
mHandle,
151+
mPath.get_native_path().c_str());
152+
} else {
153+
// Just single file watching
154+
inotify_add_watch(
155+
mHandle,
156+
mPath.get_native_path().c_str(),
157+
IN_CREATE | IN_DELETE | IN_MODIFY | IN_MOVED_TO | IN_MOVED_FROM | IN_MOVE_SELF | IN_DELETE_SELF);
158+
}
159+
}
160+
161+
162+
//[-------------------------------------------------------]
163+
//[ Namespace ]
164+
//[-------------------------------------------------------]
165+
}

0 commit comments

Comments
 (0)