mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 05:18:12 +00:00
Userland+LibCore: Update FileWatcher + its users for InodeWatcher 2.0
With the new InodeWatcher API, the old style of creating a watcher per inode will no longer work. Therefore the FileWatcher API has been updated to support multiple watches, and its users have also been refactored to the new style. At the moment, all operations done on a (Blocking)FileWatcher return Result objects, however, this may be changed in the future if it becomes too obnoxious. :^) Co-authored-by: Gunnar Beutner <gunnar@beutner.name>
This commit is contained in:
parent
fe5ca6ca27
commit
2159f90e00
12 changed files with 403 additions and 140 deletions
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2021, Nick Vella <nick@nxk.io>
|
* Copyright (c) 2021, Nick Vella <nick@nxk.io>
|
||||||
|
* Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -8,6 +9,7 @@
|
||||||
|
|
||||||
#include <AK/LexicalPath.h>
|
#include <AK/LexicalPath.h>
|
||||||
#include <AK/QuickSort.h>
|
#include <AK/QuickSort.h>
|
||||||
|
#include <Kernel/API/InodeWatcherEvent.h>
|
||||||
#include <LibCore/DirIterator.h>
|
#include <LibCore/DirIterator.h>
|
||||||
#include <LibGUI/Icon.h>
|
#include <LibGUI/Icon.h>
|
||||||
#include <LibGUI/Variant.h>
|
#include <LibGUI/Variant.h>
|
||||||
|
@ -21,12 +23,21 @@ ProjectTemplatesModel::ProjectTemplatesModel()
|
||||||
: m_templates()
|
: m_templates()
|
||||||
, m_mapping()
|
, m_mapping()
|
||||||
{
|
{
|
||||||
auto watcher_or_error = Core::FileWatcher::watch(ProjectTemplate::templates_path());
|
auto watcher_or_error = Core::FileWatcher::create();
|
||||||
if (!watcher_or_error.is_error()) {
|
if (!watcher_or_error.is_error()) {
|
||||||
m_file_watcher = watcher_or_error.release_value();
|
m_file_watcher = watcher_or_error.release_value();
|
||||||
m_file_watcher->on_change = [&](auto) {
|
m_file_watcher->on_change = [&](auto) {
|
||||||
update();
|
update();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto watch_result = m_file_watcher->add_watch(
|
||||||
|
ProjectTemplate::templates_path(),
|
||||||
|
Core::FileWatcherEvent::Type::ChildCreated
|
||||||
|
| Core::FileWatcherEvent::Type::ChildDeleted);
|
||||||
|
|
||||||
|
if (watch_result.is_error()) {
|
||||||
|
warnln("Unable to watch templates directory, templates will not automatically refresh. Error: {}", watch_result.error());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
warnln("Unable to watch templates directory, templates will not automatically refresh. Error: {}", watcher_or_error.error());
|
warnln("Unable to watch templates directory, templates will not automatically refresh. Error: {}", watcher_or_error.error());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||||
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
|
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
|
||||||
* Copyright (c) 2020, the SerenityOS developers.
|
* Copyright (c) 2020-2021, the SerenityOS developers.
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -24,6 +24,7 @@
|
||||||
#include "TerminalWrapper.h"
|
#include "TerminalWrapper.h"
|
||||||
#include <AK/LexicalPath.h>
|
#include <AK/LexicalPath.h>
|
||||||
#include <AK/StringBuilder.h>
|
#include <AK/StringBuilder.h>
|
||||||
|
#include <Kernel/API/InodeWatcherEvent.h>
|
||||||
#include <LibCore/ArgsParser.h>
|
#include <LibCore/ArgsParser.h>
|
||||||
#include <LibCore/Event.h>
|
#include <LibCore/Event.h>
|
||||||
#include <LibCore/EventLoop.h>
|
#include <LibCore/EventLoop.h>
|
||||||
|
@ -131,6 +132,24 @@ HackStudioWidget::HackStudioWidget(const String& path_to_project)
|
||||||
initialize_debugger();
|
initialize_debugger();
|
||||||
|
|
||||||
create_toolbar(toolbar_container);
|
create_toolbar(toolbar_container);
|
||||||
|
|
||||||
|
auto maybe_watcher = Core::FileWatcher::create();
|
||||||
|
if (maybe_watcher.is_error()) {
|
||||||
|
warnln("Couldn't create a file watcher, deleted files won't be noticed! Error: {}", maybe_watcher.error());
|
||||||
|
} else {
|
||||||
|
m_file_watcher = maybe_watcher.release_value();
|
||||||
|
m_file_watcher->on_change = [this](Core::FileWatcherEvent const& event) {
|
||||||
|
if (event.type != Core::FileWatcherEvent::Type::Deleted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (event.event_path.starts_with(project().root_path())) {
|
||||||
|
String relative_path = LexicalPath::relative_path(event.event_path, project().root_path());
|
||||||
|
handle_external_file_deletion(relative_path);
|
||||||
|
} else {
|
||||||
|
handle_external_file_deletion(event.event_path);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HackStudioWidget::update_actions()
|
void HackStudioWidget::update_actions()
|
||||||
|
@ -224,18 +243,12 @@ bool HackStudioWidget::open_file(const String& full_filename)
|
||||||
new_project_file = m_project->get_file(filename);
|
new_project_file = m_project->get_file(filename);
|
||||||
m_open_files.set(filename, *new_project_file);
|
m_open_files.set(filename, *new_project_file);
|
||||||
m_open_files_vector.append(filename);
|
m_open_files_vector.append(filename);
|
||||||
auto watcher_or_error = Core::FileWatcher::watch(filename);
|
|
||||||
if (!watcher_or_error.is_error()) {
|
if (!m_file_watcher.is_null()) {
|
||||||
auto& watcher = watcher_or_error.value();
|
auto watch_result = m_file_watcher->add_watch(filename, Core::FileWatcherEvent::Type::Deleted);
|
||||||
watcher->on_change = [this, filename]() {
|
if (watch_result.is_error()) {
|
||||||
struct stat st;
|
warnln("Couldn't watch '{}'", filename);
|
||||||
if (lstat(filename.characters(), &st) < 0) {
|
}
|
||||||
if (errno == ENOENT) {
|
|
||||||
handle_external_file_deletion(filename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
m_file_watchers.set(filename, watcher_or_error.release_value());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_open_files_view->model()->update();
|
m_open_files_view->model()->update();
|
||||||
|
@ -1030,7 +1043,6 @@ void HackStudioWidget::handle_external_file_deletion(const String& filepath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_file_watchers.remove(filepath);
|
|
||||||
m_open_files_view->model()->update();
|
m_open_files_view->model()->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||||
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
|
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
|
||||||
* Copyright (c) 2020, the SerenityOS developers.
|
* Copyright (c) 2020-2021, the SerenityOS developers.
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -122,7 +122,7 @@ private:
|
||||||
RefPtr<EditorWrapper> m_current_editor_wrapper;
|
RefPtr<EditorWrapper> m_current_editor_wrapper;
|
||||||
|
|
||||||
HashMap<String, NonnullRefPtr<ProjectFile>> m_open_files;
|
HashMap<String, NonnullRefPtr<ProjectFile>> m_open_files;
|
||||||
HashMap<String, NonnullRefPtr<Core::FileWatcher>> m_file_watchers;
|
RefPtr<Core::FileWatcher> m_file_watcher;
|
||||||
Vector<String> m_open_files_vector; // NOTE: This contains the keys from m_open_files and m_file_watchers
|
Vector<String> m_open_files_vector; // NOTE: This contains the keys from m_open_files and m_file_watchers
|
||||||
|
|
||||||
OwnPtr<Project> m_project;
|
OwnPtr<Project> m_project;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
|
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
|
||||||
|
* Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -158,7 +159,9 @@ private:
|
||||||
int virt$sched_getparam(pid_t, FlatPtr);
|
int virt$sched_getparam(pid_t, FlatPtr);
|
||||||
int virt$set_thread_name(pid_t, FlatPtr, size_t);
|
int virt$set_thread_name(pid_t, FlatPtr, size_t);
|
||||||
pid_t virt$setsid();
|
pid_t virt$setsid();
|
||||||
int virt$watch_file(FlatPtr, size_t);
|
int virt$create_inode_watcher(unsigned);
|
||||||
|
int virt$inode_watcher_add_watch(FlatPtr);
|
||||||
|
int virt$inode_watcher_remove_watch(int, int);
|
||||||
int virt$readlink(FlatPtr);
|
int virt$readlink(FlatPtr);
|
||||||
u32 virt$allocate_tls(FlatPtr, size_t);
|
u32 virt$allocate_tls(FlatPtr, size_t);
|
||||||
int virt$ptsname(int fd, FlatPtr buffer, size_t buffer_size);
|
int virt$ptsname(int fd, FlatPtr buffer, size_t buffer_size);
|
||||||
|
|
|
@ -222,8 +222,12 @@ u32 Emulator::virt_syscall(u32 function, u32 arg1, u32 arg2, u32 arg3)
|
||||||
return virt$set_thread_name(arg1, arg2, arg3);
|
return virt$set_thread_name(arg1, arg2, arg3);
|
||||||
case SC_setsid:
|
case SC_setsid:
|
||||||
return virt$setsid();
|
return virt$setsid();
|
||||||
case SC_watch_file:
|
case SC_create_inode_watcher:
|
||||||
return virt$watch_file(arg1, arg2);
|
return virt$create_inode_watcher(arg1);
|
||||||
|
case SC_inode_watcher_add_watch:
|
||||||
|
return virt$inode_watcher_add_watch(arg1);
|
||||||
|
case SC_inode_watcher_remove_watch:
|
||||||
|
return virt$inode_watcher_remove_watch(arg1, arg2);
|
||||||
case SC_clock_nanosleep:
|
case SC_clock_nanosleep:
|
||||||
return virt$clock_nanosleep(arg1);
|
return virt$clock_nanosleep(arg1);
|
||||||
case SC_readlink:
|
case SC_readlink:
|
||||||
|
@ -1386,10 +1390,21 @@ pid_t Emulator::virt$setsid()
|
||||||
return syscall(SC_setsid);
|
return syscall(SC_setsid);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Emulator::virt$watch_file(FlatPtr user_path_addr, size_t path_length)
|
int Emulator::virt$create_inode_watcher(unsigned flags)
|
||||||
{
|
{
|
||||||
auto user_path = mmu().copy_buffer_from_vm(user_path_addr, path_length);
|
return syscall(SC_create_inode_watcher, flags);
|
||||||
return syscall(SC_watch_file, user_path.data(), user_path.size());
|
}
|
||||||
|
|
||||||
|
int Emulator::virt$inode_watcher_add_watch(FlatPtr params_addr)
|
||||||
|
{
|
||||||
|
Syscall::SC_inode_watcher_add_watch_params params;
|
||||||
|
mmu().copy_from_vm(¶ms, params_addr, sizeof(params));
|
||||||
|
return syscall(SC_inode_watcher_add_watch, ¶ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Emulator::virt$inode_watcher_remove_watch(int fd, int wd)
|
||||||
|
{
|
||||||
|
return syscall(SC_inode_watcher_add_watch, fd, wd);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Emulator::virt$clock_nanosleep(FlatPtr params_addr)
|
int Emulator::virt$clock_nanosleep(FlatPtr params_addr)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||||
|
* Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -22,9 +23,22 @@ int fcntl(int fd, int cmd, ...)
|
||||||
__RETURN_WITH_ERRNO(rc, rc, -1);
|
__RETURN_WITH_ERRNO(rc, rc, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
int watch_file(const char* path, size_t path_length)
|
int create_inode_watcher(unsigned flags)
|
||||||
{
|
{
|
||||||
int rc = syscall(SC_watch_file, path, path_length);
|
int rc = syscall(SC_create_inode_watcher, flags);
|
||||||
|
__RETURN_WITH_ERRNO(rc, rc, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int inode_watcher_add_watch(int fd, const char* path, size_t path_length, unsigned event_mask)
|
||||||
|
{
|
||||||
|
Syscall::SC_inode_watcher_add_watch_params params { fd, { path, path_length }, event_mask };
|
||||||
|
int rc = syscall(SC_inode_watcher_add_watch, ¶ms);
|
||||||
|
__RETURN_WITH_ERRNO(rc, rc, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int inode_watcher_remove_watch(int fd, int wd)
|
||||||
|
{
|
||||||
|
int rc = syscall(SC_inode_watcher_remove_watch, fd, wd);
|
||||||
__RETURN_WITH_ERRNO(rc, rc, -1);
|
__RETURN_WITH_ERRNO(rc, rc, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||||
|
* Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -42,7 +43,9 @@ int open(const char* path, int options, ...);
|
||||||
int openat(int dirfd, const char* path, int options, ...);
|
int openat(int dirfd, const char* path, int options, ...);
|
||||||
|
|
||||||
int fcntl(int fd, int cmd, ...);
|
int fcntl(int fd, int cmd, ...);
|
||||||
int watch_file(const char* path, size_t path_length);
|
int create_inode_watcher(unsigned flags);
|
||||||
|
int inode_watcher_add_watch(int fd, const char* path, size_t path_length, unsigned event_mask);
|
||||||
|
int inode_watcher_remove_watch(int fd, int wd);
|
||||||
|
|
||||||
#define F_RDLCK 0
|
#define F_RDLCK 0
|
||||||
#define F_WRLCK 1
|
#define F_WRLCK 1
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include <AK/Result.h>
|
#include <AK/Result.h>
|
||||||
#include <AK/String.h>
|
#include <AK/String.h>
|
||||||
#include <Kernel/API/InodeWatcherEvent.h>
|
#include <Kernel/API/InodeWatcherEvent.h>
|
||||||
|
#include <Kernel/API/InodeWatcherFlags.h>
|
||||||
#include <LibCore/DirIterator.h>
|
#include <LibCore/DirIterator.h>
|
||||||
#include <LibCore/Notifier.h>
|
#include <LibCore/Notifier.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
@ -24,36 +25,154 @@
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
// Only supported in serenity mode because we use `watch_file`
|
// Only supported in serenity mode because we use InodeWatcher syscalls
|
||||||
#ifdef __serenity__
|
#ifdef __serenity__
|
||||||
|
|
||||||
static String get_child_path_from_inode_index(const String& path, unsigned child_inode_index)
|
static Optional<FileWatcherEvent> get_event_from_fd(int fd, HashMap<unsigned, String> const& wd_to_path)
|
||||||
{
|
{
|
||||||
DirIterator iterator(path, Core::DirIterator::SkipDots);
|
u8 buffer[MAXIMUM_EVENT_SIZE];
|
||||||
if (iterator.has_error()) {
|
int rc = read(fd, &buffer, MAXIMUM_EVENT_SIZE);
|
||||||
|
if (rc == 0) {
|
||||||
|
return {};
|
||||||
|
} else if (rc < 0) {
|
||||||
|
dbgln_if(FILE_WATCHER_DEBUG, "get_event_from_fd: Reading from wd {} failed: {}", fd, strerror(errno));
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
while (iterator.has_next()) {
|
InodeWatcherEvent* event = reinterpret_cast<InodeWatcherEvent*>(buffer);
|
||||||
auto child_full_path = iterator.next_full_path();
|
FileWatcherEvent result;
|
||||||
|
|
||||||
struct stat st = {};
|
auto it = wd_to_path.find(event->watch_descriptor);
|
||||||
if (lstat(child_full_path.characters(), &st) < 0) {
|
if (it == wd_to_path.end()) {
|
||||||
|
dbgln_if(FILE_WATCHER_DEBUG, "get_event_from_fd: Got an event for a non-existent wd {}?!", event->watch_descriptor);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
String const& path = it->value;
|
||||||
|
|
||||||
|
switch (event->type) {
|
||||||
|
case InodeWatcherEvent::Type::ChildCreated:
|
||||||
|
result.type = FileWatcherEvent::Type::ChildCreated;
|
||||||
|
break;
|
||||||
|
case InodeWatcherEvent::Type::ChildDeleted:
|
||||||
|
result.type = FileWatcherEvent::Type::ChildDeleted;
|
||||||
|
break;
|
||||||
|
case InodeWatcherEvent::Type::Deleted:
|
||||||
|
result.type = FileWatcherEvent::Type::Deleted;
|
||||||
|
break;
|
||||||
|
case InodeWatcherEvent::Type::ContentModified:
|
||||||
|
result.type = FileWatcherEvent::Type::ContentModified;
|
||||||
|
break;
|
||||||
|
case InodeWatcherEvent::Type::MetadataModified:
|
||||||
|
result.type = FileWatcherEvent::Type::MetadataModified;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
warnln("Unknown event type {} returned by the watch_file descriptor for {}", static_cast<unsigned>(event->type), path);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// We trust that the kernel only sends the name when appropriate.
|
||||||
|
if (event->name_length > 0) {
|
||||||
|
String child_name { event->name, event->name_length - 1 };
|
||||||
|
auto lexical_path = LexicalPath::join(path, child_name);
|
||||||
|
if (!lexical_path.is_valid()) {
|
||||||
|
dbgln_if(FILE_WATCHER_DEBUG, "get_event_from_fd: Reading from wd {}: Invalid child name '{}'", fd, child_name);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (st.st_ino == child_inode_index) {
|
result.event_path = lexical_path.string();
|
||||||
return child_full_path;
|
} else {
|
||||||
}
|
result.event_path = path;
|
||||||
}
|
}
|
||||||
return {};
|
|
||||||
|
dbgln_if(FILE_WATCHER_DEBUG, "get_event_from_fd: got event from wd {} on '{}' type {}", fd, result.event_path, result.type);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockingFileWatcher::BlockingFileWatcher(const String& path)
|
Result<bool, String> FileWatcherBase::add_watch(String path, FileWatcherEvent::Type event_mask)
|
||||||
: m_path(path)
|
{
|
||||||
|
LexicalPath lexical_path;
|
||||||
|
if (path.length() > 0 && path[0] == '/') {
|
||||||
|
lexical_path = LexicalPath { path };
|
||||||
|
} else {
|
||||||
|
char* buf = getcwd(nullptr, 0);
|
||||||
|
lexical_path = LexicalPath::join(String(buf), path);
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lexical_path.is_valid()) {
|
||||||
|
dbgln_if(FILE_WATCHER_DEBUG, "add_watch: path '{}' invalid", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const& canonical_path = lexical_path.string();
|
||||||
|
if (m_path_to_wd.find(canonical_path) != m_path_to_wd.end()) {
|
||||||
|
dbgln_if(FILE_WATCHER_DEBUG, "add_watch: path '{}' is already being watched", canonical_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto kernel_mask = InodeWatcherEvent::Type::Invalid;
|
||||||
|
if (has_flag(event_mask, FileWatcherEvent::Type::ChildCreated))
|
||||||
|
kernel_mask |= InodeWatcherEvent::Type::ChildCreated;
|
||||||
|
if (has_flag(event_mask, FileWatcherEvent::Type::ChildDeleted))
|
||||||
|
kernel_mask |= InodeWatcherEvent::Type::ChildDeleted;
|
||||||
|
if (has_flag(event_mask, FileWatcherEvent::Type::Deleted))
|
||||||
|
kernel_mask |= InodeWatcherEvent::Type::Deleted;
|
||||||
|
if (has_flag(event_mask, FileWatcherEvent::Type::ContentModified))
|
||||||
|
kernel_mask |= InodeWatcherEvent::Type::ContentModified;
|
||||||
|
if (has_flag(event_mask, FileWatcherEvent::Type::MetadataModified))
|
||||||
|
kernel_mask |= InodeWatcherEvent::Type::MetadataModified;
|
||||||
|
|
||||||
|
int wd = inode_watcher_add_watch(m_watcher_fd, canonical_path.characters(), canonical_path.length(), static_cast<unsigned>(kernel_mask));
|
||||||
|
if (wd < 0)
|
||||||
|
return String::formatted("Could not watch file '{}' : {}", canonical_path, strerror(errno));
|
||||||
|
|
||||||
|
m_path_to_wd.set(canonical_path, wd);
|
||||||
|
m_wd_to_path.set(wd, canonical_path);
|
||||||
|
|
||||||
|
dbgln_if(FILE_WATCHER_DEBUG, "add_watch: watching path '{}' on InodeWatcher {} wd {}", canonical_path, m_watcher_fd, wd);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<bool, String> FileWatcherBase::remove_watch(String path)
|
||||||
|
{
|
||||||
|
LexicalPath lexical_path;
|
||||||
|
if (path.length() > 0 && path[0] == '/') {
|
||||||
|
lexical_path = LexicalPath { path };
|
||||||
|
} else {
|
||||||
|
char* buf = getcwd(nullptr, 0);
|
||||||
|
lexical_path = LexicalPath::join(String(buf), path);
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lexical_path.is_valid()) {
|
||||||
|
dbgln_if(FILE_WATCHER_DEBUG, "remove_watch: path '{}' invalid", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const& canonical_path = lexical_path.string();
|
||||||
|
auto it = m_path_to_wd.find(canonical_path);
|
||||||
|
if (it == m_path_to_wd.end()) {
|
||||||
|
dbgln_if(FILE_WATCHER_DEBUG, "remove_watch: path '{}' is not being watched", canonical_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rc = inode_watcher_remove_watch(m_watcher_fd, it->value);
|
||||||
|
if (rc < 0) {
|
||||||
|
return String::formatted("Could not stop watching file '{}' : {}", path, strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_path_to_wd.remove(it);
|
||||||
|
m_wd_to_path.remove(it->value);
|
||||||
|
|
||||||
|
dbgln_if(FILE_WATCHER_DEBUG, "remove_watch: stopped watching path '{}' on InodeWatcher {}", canonical_path, m_watcher_fd);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockingFileWatcher::BlockingFileWatcher(InodeWatcherFlags flags)
|
||||||
|
: FileWatcherBase(create_inode_watcher(static_cast<unsigned>(flags)))
|
||||||
{
|
{
|
||||||
m_watcher_fd = watch_file(path.characters(), path.length());
|
|
||||||
VERIFY(m_watcher_fd != -1);
|
VERIFY(m_watcher_fd != -1);
|
||||||
|
dbgln_if(FILE_WATCHER_DEBUG, "BlockingFileWatcher created with InodeWatcher {}", m_watcher_fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockingFileWatcher::~BlockingFileWatcher()
|
BlockingFileWatcher::~BlockingFileWatcher()
|
||||||
|
@ -63,80 +182,51 @@ BlockingFileWatcher::~BlockingFileWatcher()
|
||||||
|
|
||||||
Optional<FileWatcherEvent> BlockingFileWatcher::wait_for_event()
|
Optional<FileWatcherEvent> BlockingFileWatcher::wait_for_event()
|
||||||
{
|
{
|
||||||
InodeWatcherEvent event {};
|
dbgln_if(FILE_WATCHER_DEBUG, "BlockingFileWatcher::wait_for_event()");
|
||||||
int rc = read(m_watcher_fd, &event, sizeof(event));
|
|
||||||
if (rc <= 0)
|
|
||||||
return {};
|
|
||||||
|
|
||||||
FileWatcherEvent result;
|
auto maybe_event = get_event_from_fd(m_watcher_fd, m_wd_to_path);
|
||||||
if (event.type == InodeWatcherEvent::Type::ChildAdded)
|
if (!maybe_event.has_value())
|
||||||
result.type = FileWatcherEvent::Type::ChildAdded;
|
return maybe_event;
|
||||||
else if (event.type == InodeWatcherEvent::Type::ChildRemoved)
|
|
||||||
result.type = FileWatcherEvent::Type::ChildRemoved;
|
|
||||||
else if (event.type == InodeWatcherEvent::Type::Modified)
|
|
||||||
result.type = FileWatcherEvent::Type::Modified;
|
|
||||||
else
|
|
||||||
return {};
|
|
||||||
|
|
||||||
if (result.type == FileWatcherEvent::Type::ChildAdded || result.type == FileWatcherEvent::Type::ChildRemoved) {
|
auto event = maybe_event.release_value();
|
||||||
auto child_path = get_child_path_from_inode_index(m_path, event.inode_index);
|
if (event.type == FileWatcherEvent::Type::Deleted) {
|
||||||
if (!LexicalPath(child_path).is_valid())
|
auto result = remove_watch(event.event_path);
|
||||||
return {};
|
if (result.is_error()) {
|
||||||
|
dbgln_if(FILE_WATCHER_DEBUG, "wait_for_event: {}", result.error());
|
||||||
result.child_path = child_path;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<NonnullRefPtr<FileWatcher>, String> FileWatcher::watch(const String& path)
|
Result<NonnullRefPtr<FileWatcher>, String> FileWatcher::create(InodeWatcherFlags flags)
|
||||||
{
|
{
|
||||||
auto watch_fd = watch_file(path.characters(), path.length());
|
auto watcher_fd = create_inode_watcher(static_cast<unsigned>(flags | InodeWatcherFlags::CloseOnExec));
|
||||||
if (watch_fd < 0) {
|
if (watcher_fd < 0) {
|
||||||
return String::formatted("Could not watch file '{}' : {}", path.characters(), strerror(errno));
|
return String::formatted("FileWatcher: Could not create InodeWatcher: {}", strerror(errno));
|
||||||
}
|
}
|
||||||
|
|
||||||
fcntl(watch_fd, F_SETFD, FD_CLOEXEC);
|
auto notifier = Notifier::construct(watcher_fd, Notifier::Event::Read);
|
||||||
if (watch_fd < 0) {
|
return adopt_ref(*new FileWatcher(watcher_fd, move(notifier)));
|
||||||
return String::formatted("Could not watch file '{}' : {}", path.characters(), strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
dbgln_if(FILE_WATCHER_DEBUG, "Started watcher for file '{}'", path.characters());
|
|
||||||
auto notifier = Notifier::construct(watch_fd, Notifier::Event::Read);
|
|
||||||
return adopt_ref(*new FileWatcher(move(notifier), move(path)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FileWatcher::FileWatcher(NonnullRefPtr<Notifier> notifier, const String& path)
|
FileWatcher::FileWatcher(int watcher_fd, NonnullRefPtr<Notifier> notifier)
|
||||||
: m_notifier(move(notifier))
|
: FileWatcherBase(watcher_fd)
|
||||||
, m_path(path)
|
, m_notifier(move(notifier))
|
||||||
{
|
{
|
||||||
m_notifier->on_ready_to_read = [this] {
|
m_notifier->on_ready_to_read = [this] {
|
||||||
InodeWatcherEvent event {};
|
auto maybe_event = get_event_from_fd(m_notifier->fd(), m_wd_to_path);
|
||||||
int rc = read(m_notifier->fd(), &event, sizeof(event));
|
if (maybe_event.has_value()) {
|
||||||
if (rc <= 0)
|
auto event = maybe_event.value();
|
||||||
return;
|
on_change(event);
|
||||||
|
|
||||||
FileWatcherEvent result;
|
if (event.type == FileWatcherEvent::Type::Deleted) {
|
||||||
if (event.type == InodeWatcherEvent::Type::ChildAdded) {
|
auto result = remove_watch(event.event_path);
|
||||||
result.type = FileWatcherEvent::Type::ChildAdded;
|
if (result.is_error()) {
|
||||||
} else if (event.type == InodeWatcherEvent::Type::ChildRemoved) {
|
dbgln_if(FILE_WATCHER_DEBUG, "on_ready_to_read: {}", result.error());
|
||||||
result.type = FileWatcherEvent::Type::ChildRemoved;
|
}
|
||||||
} else if (event.type == InodeWatcherEvent::Type::Modified) {
|
}
|
||||||
result.type = FileWatcherEvent::Type::Modified;
|
|
||||||
} else {
|
|
||||||
warnln("Unknown event type {} returned by the watch_file descriptor for {}", (unsigned)event.type, m_path.characters());
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.type == FileWatcherEvent::Type::ChildAdded || result.type == FileWatcherEvent::Type::ChildRemoved) {
|
|
||||||
auto child_path = get_child_path_from_inode_index(m_path, event.inode_index);
|
|
||||||
if (!LexicalPath(child_path).is_valid())
|
|
||||||
return;
|
|
||||||
|
|
||||||
result.child_path = child_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
on_change(result);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +234,7 @@ FileWatcher::~FileWatcher()
|
||||||
{
|
{
|
||||||
m_notifier->on_ready_to_read = nullptr;
|
m_notifier->on_ready_to_read = nullptr;
|
||||||
close(m_notifier->fd());
|
close(m_notifier->fd());
|
||||||
dbgln_if(FILE_WATCHER_DEBUG, "Ended watcher for file '{}'", m_path.characters());
|
dbgln_if(FILE_WATCHER_DEBUG, "Stopped watcher at fd {}", m_notifier->fd());
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -7,54 +7,118 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/EnumBits.h>
|
||||||
#include <AK/Function.h>
|
#include <AK/Function.h>
|
||||||
#include <AK/Noncopyable.h>
|
#include <AK/Noncopyable.h>
|
||||||
#include <AK/NonnullRefPtr.h>
|
#include <AK/NonnullRefPtr.h>
|
||||||
#include <AK/RefCounted.h>
|
#include <AK/RefCounted.h>
|
||||||
#include <AK/Result.h>
|
#include <AK/Result.h>
|
||||||
#include <AK/String.h>
|
#include <AK/String.h>
|
||||||
|
#include <Kernel/API/InodeWatcherEvent.h>
|
||||||
|
#include <Kernel/API/InodeWatcherFlags.h>
|
||||||
#include <LibCore/Notifier.h>
|
#include <LibCore/Notifier.h>
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
struct FileWatcherEvent {
|
struct FileWatcherEvent {
|
||||||
enum class Type {
|
enum class Type {
|
||||||
Modified,
|
Invalid = 0,
|
||||||
ChildAdded,
|
MetadataModified = 1 << 0,
|
||||||
ChildRemoved,
|
ContentModified = 1 << 1,
|
||||||
|
Deleted = 1 << 2,
|
||||||
|
ChildCreated = 1 << 3,
|
||||||
|
ChildDeleted = 1 << 4,
|
||||||
};
|
};
|
||||||
Type type;
|
Type type;
|
||||||
String child_path;
|
String event_path;
|
||||||
};
|
};
|
||||||
|
|
||||||
class BlockingFileWatcher {
|
AK_ENUM_BITWISE_OPERATORS(FileWatcherEvent::Type);
|
||||||
|
|
||||||
|
class FileWatcherBase {
|
||||||
|
public:
|
||||||
|
virtual ~FileWatcherBase() { }
|
||||||
|
|
||||||
|
Result<bool, String> add_watch(String path, FileWatcherEvent::Type event_mask);
|
||||||
|
Result<bool, String> remove_watch(String path);
|
||||||
|
bool is_watching(String const& path) const { return m_path_to_wd.find(path) != m_path_to_wd.end(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
FileWatcherBase(int watcher_fd)
|
||||||
|
: m_watcher_fd(watcher_fd)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int m_watcher_fd { -1 };
|
||||||
|
HashMap<String, unsigned> m_path_to_wd;
|
||||||
|
HashMap<unsigned, String> m_wd_to_path;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BlockingFileWatcher final : public FileWatcherBase {
|
||||||
AK_MAKE_NONCOPYABLE(BlockingFileWatcher);
|
AK_MAKE_NONCOPYABLE(BlockingFileWatcher);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit BlockingFileWatcher(const String& path);
|
explicit BlockingFileWatcher(InodeWatcherFlags = InodeWatcherFlags::None);
|
||||||
~BlockingFileWatcher();
|
~BlockingFileWatcher();
|
||||||
|
|
||||||
Optional<FileWatcherEvent> wait_for_event();
|
Optional<FileWatcherEvent> wait_for_event();
|
||||||
|
|
||||||
private:
|
|
||||||
String m_path;
|
|
||||||
int m_watcher_fd { -1 };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class FileWatcher : public RefCounted<FileWatcher> {
|
class FileWatcher final : public FileWatcherBase
|
||||||
|
, public RefCounted<FileWatcher> {
|
||||||
AK_MAKE_NONCOPYABLE(FileWatcher);
|
AK_MAKE_NONCOPYABLE(FileWatcher);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static Result<NonnullRefPtr<FileWatcher>, String> watch(const String& path);
|
static Result<NonnullRefPtr<FileWatcher>, String> create(InodeWatcherFlags = InodeWatcherFlags::None);
|
||||||
~FileWatcher();
|
~FileWatcher();
|
||||||
|
|
||||||
Function<void(FileWatcherEvent)> on_change;
|
Function<void(FileWatcherEvent const&)> on_change;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FileWatcher(NonnullRefPtr<Notifier>, const String& path);
|
FileWatcher(int watcher_fd, NonnullRefPtr<Notifier>);
|
||||||
|
|
||||||
NonnullRefPtr<Notifier> m_notifier;
|
NonnullRefPtr<Notifier> m_notifier;
|
||||||
String m_path;
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace AK {
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct Formatter<Core::FileWatcherEvent> : Formatter<FormatString> {
|
||||||
|
void format(FormatBuilder& builder, const Core::FileWatcherEvent& value)
|
||||||
|
{
|
||||||
|
Formatter<FormatString>::format(builder, "FileWatcherEvent(\"{}\", {})", value.event_path, value.type);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct Formatter<Core::FileWatcherEvent::Type> : Formatter<FormatString> {
|
||||||
|
void format(FormatBuilder& builder, const Core::FileWatcherEvent::Type& value)
|
||||||
|
{
|
||||||
|
char const* type;
|
||||||
|
switch (value) {
|
||||||
|
case Core::FileWatcherEvent::Type::ChildCreated:
|
||||||
|
type = "ChildCreated";
|
||||||
|
break;
|
||||||
|
case Core::FileWatcherEvent::Type::ChildDeleted:
|
||||||
|
type = "ChildDeleted";
|
||||||
|
break;
|
||||||
|
case Core::FileWatcherEvent::Type::Deleted:
|
||||||
|
type = "Deleted";
|
||||||
|
break;
|
||||||
|
case Core::FileWatcherEvent::Type::ContentModified:
|
||||||
|
type = "ContentModified";
|
||||||
|
break;
|
||||||
|
case Core::FileWatcherEvent::Type::MetadataModified:
|
||||||
|
type = "MetadataModified";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.put_string(type);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||||
|
* Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -125,21 +126,18 @@ void FileSystemModel::Node::traverse_if_needed()
|
||||||
children.append(move(directory_children));
|
children.append(move(directory_children));
|
||||||
children.append(move(file_children));
|
children.append(move(file_children));
|
||||||
|
|
||||||
if (!m_file_watcher) {
|
if (!m_model.m_file_watcher->is_watching(full_path)) {
|
||||||
|
// We are not already watching this file, watch it
|
||||||
|
auto result = m_model.m_file_watcher->add_watch(full_path,
|
||||||
|
Core::FileWatcherEvent::Type::MetadataModified
|
||||||
|
| Core::FileWatcherEvent::Type::ChildCreated
|
||||||
|
| Core::FileWatcherEvent::Type::ChildDeleted
|
||||||
|
| Core::FileWatcherEvent::Type::Deleted);
|
||||||
|
|
||||||
// We are not already watching this file, create a new watcher
|
if (result.is_error()) {
|
||||||
auto watcher_or_error = Core::FileWatcher::watch(full_path);
|
dbgln("Couldn't watch '{}': {}", full_path, result.error());
|
||||||
|
} else if (result.value() == false) {
|
||||||
// Note : the watcher may not be created (e.g. we do not have access rights.) This is expected, just don't watch if that's the case.
|
dbgln("Couldn't watch '{}', probably already watching", full_path);
|
||||||
if (!watcher_or_error.is_error()) {
|
|
||||||
m_file_watcher = watcher_or_error.release_value();
|
|
||||||
m_file_watcher->on_change = [this](auto) {
|
|
||||||
has_traversed = false;
|
|
||||||
mode = 0;
|
|
||||||
children.clear();
|
|
||||||
reify_if_needed();
|
|
||||||
m_model.did_update();
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,10 +179,29 @@ String FileSystemModel::Node::full_path() const
|
||||||
|
|
||||||
ModelIndex FileSystemModel::index(String path, int column) const
|
ModelIndex FileSystemModel::index(String path, int column) const
|
||||||
{
|
{
|
||||||
LexicalPath lexical_path(move(path));
|
Node const* node = node_for_path(move(path));
|
||||||
|
if (node != nullptr) {
|
||||||
|
return node->index(column);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSystemModel::Node const* FileSystemModel::node_for_path(String const& path) const
|
||||||
|
{
|
||||||
|
LexicalPath lexical_path;
|
||||||
|
if (path == m_root_path) {
|
||||||
|
lexical_path = LexicalPath { "/" };
|
||||||
|
} else if (!m_root_path.is_empty() && path.starts_with(m_root_path)) {
|
||||||
|
lexical_path = LexicalPath { LexicalPath::relative_path(path, m_root_path) };
|
||||||
|
} else {
|
||||||
|
lexical_path = LexicalPath { move(path) };
|
||||||
|
}
|
||||||
|
|
||||||
const Node* node = m_root->m_parent_of_root ? &m_root->children.first() : m_root;
|
const Node* node = m_root->m_parent_of_root ? &m_root->children.first() : m_root;
|
||||||
if (lexical_path.string() == "/")
|
if (lexical_path.string() == "/")
|
||||||
return node->index(column);
|
return node;
|
||||||
|
|
||||||
for (size_t i = 0; i < lexical_path.parts().size(); ++i) {
|
for (size_t i = 0; i < lexical_path.parts().size(); ++i) {
|
||||||
auto& part = lexical_path.parts()[i];
|
auto& part = lexical_path.parts()[i];
|
||||||
bool found = false;
|
bool found = false;
|
||||||
|
@ -194,14 +211,14 @@ ModelIndex FileSystemModel::index(String path, int column) const
|
||||||
node = &child;
|
node = &child;
|
||||||
found = true;
|
found = true;
|
||||||
if (i == lexical_path.parts().size() - 1)
|
if (i == lexical_path.parts().size() - 1)
|
||||||
return child.index(column);
|
return node;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!found)
|
if (!found)
|
||||||
return {};
|
return nullptr;
|
||||||
}
|
}
|
||||||
return {};
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
String FileSystemModel::full_path(const ModelIndex& index) const
|
String FileSystemModel::full_path(const ModelIndex& index) const
|
||||||
|
@ -225,6 +242,31 @@ FileSystemModel::FileSystemModel(String root_path, Mode mode)
|
||||||
m_group_names.set(group->gr_gid, group->gr_name);
|
m_group_names.set(group->gr_gid, group->gr_name);
|
||||||
endgrent();
|
endgrent();
|
||||||
|
|
||||||
|
auto result = Core::FileWatcher::create();
|
||||||
|
if (result.is_error()) {
|
||||||
|
dbgln("{}", result.error());
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_file_watcher = result.release_value();
|
||||||
|
m_file_watcher->on_change = [this](Core::FileWatcherEvent const& event) {
|
||||||
|
Node const* maybe_node = node_for_path(event.event_path);
|
||||||
|
if (maybe_node == nullptr) {
|
||||||
|
dbgln("Received event at \"{}\" but we don't have that node", event.event_path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto& node = *const_cast<Node*>(maybe_node);
|
||||||
|
|
||||||
|
dbgln("Event at \"{}\" on Node {}: {}", node.full_path(), &node, event);
|
||||||
|
|
||||||
|
// FIXME: Your time is coming, un-granular updates.
|
||||||
|
node.has_traversed = false;
|
||||||
|
node.mode = 0;
|
||||||
|
node.children.clear();
|
||||||
|
node.reify_if_needed();
|
||||||
|
did_update();
|
||||||
|
};
|
||||||
|
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,8 +87,6 @@ public:
|
||||||
|
|
||||||
bool m_selected { false };
|
bool m_selected { false };
|
||||||
|
|
||||||
RefPtr<Core::FileWatcher> m_file_watcher;
|
|
||||||
|
|
||||||
int m_error { 0 };
|
int m_error { 0 };
|
||||||
bool m_parent_of_root { false };
|
bool m_parent_of_root { false };
|
||||||
|
|
||||||
|
@ -148,6 +146,8 @@ private:
|
||||||
String name_for_uid(uid_t) const;
|
String name_for_uid(uid_t) const;
|
||||||
String name_for_gid(gid_t) const;
|
String name_for_gid(gid_t) const;
|
||||||
|
|
||||||
|
Node const* node_for_path(String const&) const;
|
||||||
|
|
||||||
HashMap<uid_t, String> m_user_names;
|
HashMap<uid_t, String> m_user_names;
|
||||||
HashMap<gid_t, String> m_group_names;
|
HashMap<gid_t, String> m_group_names;
|
||||||
|
|
||||||
|
@ -162,6 +162,8 @@ private:
|
||||||
unsigned m_thumbnail_progress_total { 0 };
|
unsigned m_thumbnail_progress_total { 0 };
|
||||||
|
|
||||||
bool m_should_show_dotfiles { false };
|
bool m_should_show_dotfiles { false };
|
||||||
|
|
||||||
|
RefPtr<Core::FileWatcher> m_file_watcher;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include <AK/LexicalPath.h>
|
#include <AK/LexicalPath.h>
|
||||||
#include <AK/MappedFile.h>
|
#include <AK/MappedFile.h>
|
||||||
|
#include <Kernel/API/InodeWatcherEvent.h>
|
||||||
#include <LibCompress/Gzip.h>
|
#include <LibCompress/Gzip.h>
|
||||||
#include <LibCore/File.h>
|
#include <LibCore/File.h>
|
||||||
#include <LibCore/FileWatcher.h>
|
#include <LibCore/FileWatcher.h>
|
||||||
|
@ -99,13 +100,19 @@ int main()
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Core::BlockingFileWatcher watcher { "/tmp/coredump" };
|
Core::BlockingFileWatcher watcher;
|
||||||
|
auto watch_result = watcher.add_watch("/tmp/coredump", Core::FileWatcherEvent::Type::ChildCreated);
|
||||||
|
if (watch_result.is_error()) {
|
||||||
|
warnln("Failed to watch the coredump directory: {}", watch_result.error());
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
auto event = watcher.wait_for_event();
|
auto event = watcher.wait_for_event();
|
||||||
VERIFY(event.has_value());
|
VERIFY(event.has_value());
|
||||||
if (event.value().type != Core::FileWatcherEvent::Type::ChildAdded)
|
if (event.value().type != Core::FileWatcherEvent::Type::ChildCreated)
|
||||||
continue;
|
continue;
|
||||||
auto coredump_path = event.value().child_path;
|
auto& coredump_path = event.value().event_path;
|
||||||
if (coredump_path.ends_with(".gz"))
|
if (coredump_path.ends_with(".gz"))
|
||||||
continue; // stops compress_coredump from accidentally triggering us
|
continue; // stops compress_coredump from accidentally triggering us
|
||||||
dbgln("New coredump file: {}", coredump_path);
|
dbgln("New coredump file: {}", coredump_path);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue