mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-04 05:20:30 +00:00
LibCore: Improve support for the macOS file watcher with actual files
When asked to monitor a file (not a directory), we often need to instead monitor the parent directory to receive FS events. For example, when a symbolic link is deleted/created, we don't receive any events unless we are watching the parent.
This commit is contained in:
parent
574b4be433
commit
9f496a9c65
Notes:
github-actions[bot]
2024-08-25 07:48:58 +00:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/9f496a9c65e Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/1182
2 changed files with 74 additions and 6 deletions
|
@ -4,9 +4,13 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/LexicalPath.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibCore/FileWatcher.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibCore/Timer.h>
|
||||
#include <LibFileSystem/FileSystem.h>
|
||||
#include <LibTest/TestCase.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
@ -61,3 +65,45 @@ TEST_CASE(file_watcher_child_events)
|
|||
|
||||
event_loop.exec();
|
||||
}
|
||||
|
||||
TEST_CASE(contents_changed)
|
||||
{
|
||||
auto event_loop = Core::EventLoop();
|
||||
|
||||
auto temp_path = MUST(FileSystem::real_path("/tmp"sv));
|
||||
auto test_path = LexicalPath::join(temp_path, "testfile"sv);
|
||||
|
||||
auto write_file = [&](auto contents) {
|
||||
auto file = MUST(Core::File::open(test_path.string(), Core::File::OpenMode::Write));
|
||||
MUST(file->write_until_depleted(contents));
|
||||
};
|
||||
|
||||
write_file("line1\n"sv);
|
||||
|
||||
auto file_watcher = MUST(Core::FileWatcher::create());
|
||||
MUST(file_watcher->add_watch(test_path.string(), Core::FileWatcherEvent::Type::ContentModified));
|
||||
|
||||
int event_count = 0;
|
||||
file_watcher->on_change = [&](Core::FileWatcherEvent const& event) {
|
||||
EXPECT_EQ(event.event_path, test_path.string());
|
||||
EXPECT(has_flag(event.type, Core::FileWatcherEvent::Type::ContentModified));
|
||||
|
||||
if (++event_count == 2) {
|
||||
MUST(Core::System::unlink(test_path.string()));
|
||||
event_loop.quit(0);
|
||||
}
|
||||
};
|
||||
|
||||
auto timer1 = Core::Timer::create_single_shot(500, [&] { write_file("line2\n"sv); });
|
||||
timer1->start();
|
||||
|
||||
auto timer2 = Core::Timer::create_single_shot(1000, [&] { write_file("line3\n"sv); });
|
||||
timer2->start();
|
||||
|
||||
auto catchall_timer = Core::Timer::create_single_shot(2000, [&] {
|
||||
VERIFY_NOT_REACHED();
|
||||
});
|
||||
catchall_timer->start();
|
||||
|
||||
event_loop.exec();
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ namespace Core {
|
|||
struct MonitoredPath {
|
||||
ByteString path;
|
||||
FileWatcherEvent::Type event_mask { FileWatcherEvent::Type::Invalid };
|
||||
bool is_directory { false };
|
||||
};
|
||||
|
||||
static void on_file_system_event(ConstFSEventStreamRef, void*, size_t, void*, FSEventStreamEventFlags const[], FSEventStreamEventId const[]);
|
||||
|
@ -36,6 +37,13 @@ static ErrorOr<ino_t> inode_id_from_path(StringView path)
|
|||
return stat.st_ino;
|
||||
}
|
||||
|
||||
static ErrorOr<bool> is_directory(StringView path)
|
||||
{
|
||||
// We cannot use FileSystem::is_directory as LibFileSystem depends on LibCore.
|
||||
auto stat = TRY(System::stat(path));
|
||||
return S_ISDIR(stat.st_mode);
|
||||
}
|
||||
|
||||
class FileWatcherMacOS final : public FileWatcher {
|
||||
AK_MAKE_NONCOPYABLE(FileWatcherMacOS);
|
||||
|
||||
|
@ -64,6 +72,10 @@ public:
|
|||
|
||||
ErrorOr<bool> add_watch(ByteString path, FileWatcherEvent::Type event_mask)
|
||||
{
|
||||
auto path_is_directory = TRY(is_directory(path));
|
||||
if (!path_is_directory)
|
||||
path = LexicalPath { move(path) }.parent().string();
|
||||
|
||||
if (m_path_to_inode_id.contains(path)) {
|
||||
dbgln_if(FILE_WATCHER_DEBUG, "add_watch: path '{}' is already being watched", path);
|
||||
return false;
|
||||
|
@ -71,7 +83,7 @@ public:
|
|||
|
||||
auto inode_id = TRY(inode_id_from_path(path));
|
||||
TRY(m_path_to_inode_id.try_set(path, inode_id));
|
||||
TRY(m_inode_id_to_path.try_set(inode_id, { path, event_mask }));
|
||||
TRY(m_inode_id_to_path.try_set(inode_id, { path, event_mask, path_is_directory }));
|
||||
|
||||
TRY(refresh_monitored_paths());
|
||||
|
||||
|
@ -81,6 +93,9 @@ public:
|
|||
|
||||
ErrorOr<bool> remove_watch(ByteString path)
|
||||
{
|
||||
if (!TRY(is_directory(path)))
|
||||
path = LexicalPath { move(path) }.parent().string();
|
||||
|
||||
auto it = m_path_to_inode_id.find(path);
|
||||
if (it == m_path_to_inode_id.end()) {
|
||||
dbgln_if(FILE_WATCHER_DEBUG, "remove_watch: path '{}' is not being watched", path);
|
||||
|
@ -109,7 +124,8 @@ public:
|
|||
|
||||
return MonitoredPath {
|
||||
LexicalPath::join(it->value.path, lexical_path.basename()).string(),
|
||||
it->value.event_mask
|
||||
it->value.event_mask,
|
||||
it->value.is_directory
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -225,10 +241,16 @@ void on_file_system_event(ConstFSEventStreamRef, void* user_data, size_t event_s
|
|||
event.event_path = move(monitored_path.path);
|
||||
|
||||
auto flags = event_flags[i];
|
||||
if ((flags & kFSEventStreamEventFlagItemCreated) != 0)
|
||||
event.type |= FileWatcherEvent::Type::ChildCreated;
|
||||
if ((flags & kFSEventStreamEventFlagItemRemoved) != 0)
|
||||
event.type |= FileWatcherEvent::Type::ChildDeleted;
|
||||
if ((flags & kFSEventStreamEventFlagItemCreated) != 0) {
|
||||
if (monitored_path.is_directory)
|
||||
event.type |= FileWatcherEvent::Type::ChildCreated;
|
||||
}
|
||||
if ((flags & kFSEventStreamEventFlagItemRemoved) != 0) {
|
||||
if (monitored_path.is_directory)
|
||||
event.type |= FileWatcherEvent::Type::ChildDeleted;
|
||||
else
|
||||
event.type |= FileWatcherEvent::Type::Deleted;
|
||||
}
|
||||
if ((flags & kFSEventStreamEventFlagItemModified) != 0)
|
||||
event.type |= FileWatcherEvent::Type::ContentModified;
|
||||
if ((flags & kFSEventStreamEventFlagItemInodeMetaMod) != 0)
|
||||
|
|
Loading…
Reference in a new issue