Use in-house inotify watcher
Signed-off-by: Slendi <slendi@socopon.com>
This commit is contained in:
@@ -91,19 +91,6 @@ FetchContent_Declare(
|
|||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(cpptrace)
|
FetchContent_MakeAvailable(cpptrace)
|
||||||
|
|
||||||
FetchContent_Declare(
|
|
||||||
inotify-cpp
|
|
||||||
GIT_REPOSITORY https://github.com/erikzenker/inotify-cpp.git
|
|
||||||
GIT_TAG "v1.0.0"
|
|
||||||
GIT_SHALLOW 1
|
|
||||||
)
|
|
||||||
set(BUILD_EXAMPLE OFF)
|
|
||||||
set(BUILD_SHARED_LIBS OFF)
|
|
||||||
set(BUILD_STATIC_LIBS ON)
|
|
||||||
set(BUILD_TEST OFF)
|
|
||||||
set(USE_BOOST_FILESYSTEM OFF)
|
|
||||||
FetchContent_MakeAvailable(inotify-cpp)
|
|
||||||
|
|
||||||
add_subdirectory(vendor)
|
add_subdirectory(vendor)
|
||||||
|
|
||||||
find_program(WAYLAND_SCANNER wayland-scanner REQUIRED)
|
find_program(WAYLAND_SCANNER wayland-scanner REQUIRED)
|
||||||
@@ -204,6 +191,7 @@ add_executable(waylight
|
|||||||
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/IconRegistry.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/IconRegistry.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Cache.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/Cache.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/InotifyWatcher.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/TextRenderer.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/TextRenderer.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/ImGui.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/ImGui.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/App.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/App.cpp
|
||||||
@@ -235,7 +223,6 @@ target_link_libraries(waylight PRIVATE
|
|||||||
msdfgen::msdfgen-ext
|
msdfgen::msdfgen-ext
|
||||||
lunasvg::lunasvg
|
lunasvg::lunasvg
|
||||||
SQLiteCpp
|
SQLiteCpp
|
||||||
inotify-cpp-static
|
|
||||||
|
|
||||||
m
|
m
|
||||||
dl
|
dl
|
||||||
@@ -247,4 +234,3 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
install(TARGETS waylight RUNTIME DESTINATION bin)
|
install(TARGETS waylight RUNTIME DESTINATION bin)
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
|
|
||||||
#include "common.hpp"
|
#include "common.hpp"
|
||||||
|
#include <sys/inotify.h>
|
||||||
|
|
||||||
void replace_all(
|
void replace_all(
|
||||||
std::string &str, std::string const &from, std::string const &to)
|
std::string &str, std::string const &from, std::string const &to)
|
||||||
@@ -88,25 +89,37 @@ Cache::Cache(std::shared_ptr<SQLite::Database> db)
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (auto const &dir : m_app_dirs) {
|
for (auto const &dir : m_app_dirs) {
|
||||||
m_inot.watchPathRecursively(dir);
|
m_inotify.watch_path_recursively(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto events = {
|
m_inotify.set_callback([this](FileWatchEvent const &event) {
|
||||||
inotify::Event::open | inotify::Event::is_dir,
|
auto const mask = event.mask;
|
||||||
inotify::Event::create,
|
if (mask & IN_Q_OVERFLOW) {
|
||||||
inotify::Event::modify,
|
rescan();
|
||||||
inotify::Event::remove,
|
return;
|
||||||
inotify::Event::move,
|
}
|
||||||
};
|
|
||||||
m_inot.onEvents(events, [this](auto notification) { rescan(); });
|
|
||||||
|
|
||||||
m_inot_th = std::thread([this]() { m_inot.run(); });
|
if (mask & IN_ISDIR) {
|
||||||
|
rescan();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mask & (IN_CREATE | IN_DELETE | IN_MODIFY | IN_MOVED_FROM
|
||||||
|
| IN_MOVED_TO | IN_CLOSE_WRITE)) {
|
||||||
|
if (event.path.extension() == ".desktop") {
|
||||||
|
rescan();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
m_inotify_thread = std::thread([this]() { m_inotify.run(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
Cache::~Cache()
|
Cache::~Cache()
|
||||||
{
|
{
|
||||||
m_inot.stop();
|
m_inotify.stop();
|
||||||
m_inot_th.join();
|
if (m_inotify_thread.joinable())
|
||||||
|
m_inotify_thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Cache::rescan()
|
void Cache::rescan()
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
#include <variant>
|
#include <variant>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "InotifyWatcher.hpp"
|
||||||
#include <SQLiteCpp/Database.h>
|
#include <SQLiteCpp/Database.h>
|
||||||
#include <inotify-cpp/NotifierBuilder.h>
|
|
||||||
|
|
||||||
namespace Waylight {
|
namespace Waylight {
|
||||||
|
|
||||||
@@ -59,8 +59,8 @@ private:
|
|||||||
std::vector<std::filesystem::path> m_app_dirs;
|
std::vector<std::filesystem::path> m_app_dirs;
|
||||||
std::shared_ptr<SQLite::Database> m_db;
|
std::shared_ptr<SQLite::Database> m_db;
|
||||||
|
|
||||||
inotify::NotifierBuilder m_inot;
|
InotifyWatcher m_inotify;
|
||||||
std::thread m_inot_th;
|
std::thread m_inotify_thread;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Cache
|
} // namespace Waylight
|
||||||
|
|||||||
241
src/InotifyWatcher.cpp
Normal file
241
src/InotifyWatcher.cpp
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
#include "InotifyWatcher.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstring>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <system_error>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <sys/inotify.h>
|
||||||
|
|
||||||
|
namespace Waylight {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr std::uint32_t watch_mask { IN_ATTRIB | IN_CREATE | IN_DELETE
|
||||||
|
| IN_DELETE_SELF | IN_MODIFY | IN_MOVE_SELF | IN_MOVED_FROM | IN_MOVED_TO
|
||||||
|
| IN_CLOSE_WRITE };
|
||||||
|
constexpr int poll_timeout_ms { 250 };
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
InotifyWatcher::InotifyWatcher()
|
||||||
|
: m_fd(inotify_init1(IN_CLOEXEC))
|
||||||
|
{
|
||||||
|
if (m_fd < 0) {
|
||||||
|
throw std::system_error(
|
||||||
|
errno, std::generic_category(), "inotify_init1 failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InotifyWatcher::~InotifyWatcher()
|
||||||
|
{
|
||||||
|
stop();
|
||||||
|
|
||||||
|
if (m_fd >= 0) {
|
||||||
|
::close(m_fd);
|
||||||
|
m_fd = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InotifyWatcher::watch_path_recursively(std::filesystem::path const &path)
|
||||||
|
{
|
||||||
|
add_watch_recursively(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InotifyWatcher::set_callback(callback_t cb)
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(m_watch_mutex);
|
||||||
|
m_callback = std::move(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InotifyWatcher::run()
|
||||||
|
{
|
||||||
|
if (m_running.exchange(true)) {
|
||||||
|
throw std::runtime_error("InotifyWatcher::run called while running");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<char, 4096> buffer {};
|
||||||
|
while (m_running.load()) {
|
||||||
|
pollfd fd_set {
|
||||||
|
.fd = m_fd,
|
||||||
|
.events = POLLIN,
|
||||||
|
.revents = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
int const poll_res { ::poll(&fd_set, 1, poll_timeout_ms) };
|
||||||
|
if (!m_running.load())
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (poll_res < 0) {
|
||||||
|
if (errno == EINTR)
|
||||||
|
continue;
|
||||||
|
if (errno == EBADF || errno == EINVAL)
|
||||||
|
break;
|
||||||
|
throw std::system_error(errno, std::generic_category(), "poll");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (poll_res == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (fd_set.revents & (POLLERR | POLLHUP | POLLNVAL))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (!(fd_set.revents & POLLIN))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ssize_t const bytes_read { ::read(m_fd, buffer.data(), buffer.size()) };
|
||||||
|
if (bytes_read < 0) {
|
||||||
|
if (errno == EINTR || errno == EAGAIN)
|
||||||
|
continue;
|
||||||
|
if (errno == EBADF || errno == EINVAL)
|
||||||
|
break;
|
||||||
|
throw std::system_error(errno, std::generic_category(), "read");
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t offset { 0 };
|
||||||
|
while (offset + static_cast<ssize_t>(sizeof(inotify_event))
|
||||||
|
<= bytes_read) {
|
||||||
|
auto const *event = reinterpret_cast<inotify_event const *>(
|
||||||
|
buffer.data() + offset);
|
||||||
|
std::string_view name;
|
||||||
|
if (event->len > 0) {
|
||||||
|
auto const *raw_name
|
||||||
|
= buffer.data() + offset + sizeof(inotify_event);
|
||||||
|
auto const *end
|
||||||
|
= std::find(raw_name, raw_name + event->len, '\0');
|
||||||
|
name = std::string_view(raw_name, end - raw_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch_event(event->wd, event->mask, name);
|
||||||
|
|
||||||
|
offset += sizeof(inotify_event) + event->len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_running.store(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InotifyWatcher::stop() { m_running.store(false); }
|
||||||
|
|
||||||
|
void InotifyWatcher::add_watch_for_path(std::filesystem::path const &path)
|
||||||
|
{
|
||||||
|
auto normalized { normalize_path(path) };
|
||||||
|
if (normalized.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto const key = normalized.string();
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(m_watch_mutex);
|
||||||
|
auto it = m_path_to_watch.find(key);
|
||||||
|
if (it != m_path_to_watch.end())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int const wd { ::inotify_add_watch(m_fd, normalized.c_str(), watch_mask) };
|
||||||
|
if (wd < 0) {
|
||||||
|
if (errno == ENOENT || errno == ENOTDIR)
|
||||||
|
return;
|
||||||
|
throw std::system_error(errno, std::generic_category(),
|
||||||
|
"inotify_add_watch failed for " + normalized.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::scoped_lock lock(m_watch_mutex);
|
||||||
|
m_watch_to_path.emplace(wd, normalized);
|
||||||
|
m_path_to_watch.emplace(key, wd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InotifyWatcher::add_watch_recursively(std::filesystem::path const &path)
|
||||||
|
{
|
||||||
|
auto normalized = normalize_path(path);
|
||||||
|
if (normalized.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
add_watch_for_path(normalized);
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
if (!std::filesystem::is_directory(normalized, ec))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto const options {
|
||||||
|
std::filesystem::directory_options::follow_directory_symlink
|
||||||
|
| std::filesystem::directory_options::skip_permission_denied
|
||||||
|
};
|
||||||
|
for (auto const &entry : std::filesystem::recursive_directory_iterator(
|
||||||
|
normalized, options)) {
|
||||||
|
std::error_code inner_ec;
|
||||||
|
if (!entry.is_directory(inner_ec) || inner_ec)
|
||||||
|
continue;
|
||||||
|
add_watch_for_path(entry.path());
|
||||||
|
}
|
||||||
|
} catch (std::filesystem::filesystem_error const &) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InotifyWatcher::remove_watch(int wd)
|
||||||
|
{
|
||||||
|
std::filesystem::path removed_path;
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(m_watch_mutex);
|
||||||
|
auto it { m_watch_to_path.find(wd) };
|
||||||
|
if (it == m_watch_to_path.end())
|
||||||
|
return;
|
||||||
|
removed_path = it->second;
|
||||||
|
m_watch_to_path.erase(it);
|
||||||
|
m_path_to_watch.erase(removed_path.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
::inotify_rm_watch(m_fd, wd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InotifyWatcher::dispatch_event(
|
||||||
|
int wd, std::uint32_t mask, std::string_view name)
|
||||||
|
{
|
||||||
|
std::filesystem::path base_path;
|
||||||
|
callback_t callback_copy;
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(m_watch_mutex);
|
||||||
|
auto it { m_watch_to_path.find(wd) };
|
||||||
|
if (it == m_watch_to_path.end())
|
||||||
|
return;
|
||||||
|
base_path = it->second;
|
||||||
|
callback_copy = m_callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto event_path { base_path };
|
||||||
|
if (!name.empty())
|
||||||
|
event_path /= name;
|
||||||
|
|
||||||
|
if ((mask & IN_ISDIR)
|
||||||
|
&& (mask & (IN_CREATE | IN_MOVED_TO | IN_ATTRIB | IN_CLOSE_WRITE))) {
|
||||||
|
add_watch_recursively(event_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mask & (IN_DELETE_SELF | IN_MOVE_SELF | IN_IGNORED)) {
|
||||||
|
remove_watch(wd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callback_copy) {
|
||||||
|
callback_copy(FileWatchEvent {
|
||||||
|
.path = std::move(event_path),
|
||||||
|
.mask = mask,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path InotifyWatcher::normalize_path(
|
||||||
|
std::filesystem::path const &path)
|
||||||
|
{
|
||||||
|
std::error_code ec;
|
||||||
|
auto normalized { std::filesystem::weakly_canonical(path, ec) };
|
||||||
|
if (ec)
|
||||||
|
normalized = std::filesystem::absolute(path, ec);
|
||||||
|
if (ec)
|
||||||
|
return {};
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Waylight
|
||||||
57
src/InotifyWatcher.hpp
Normal file
57
src/InotifyWatcher.hpp
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace Waylight {
|
||||||
|
|
||||||
|
struct FileWatchEvent {
|
||||||
|
std::filesystem::path path;
|
||||||
|
std::uint32_t mask;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InotifyWatcher {
|
||||||
|
public:
|
||||||
|
using callback_t = std::function<void(FileWatchEvent const &)>;
|
||||||
|
|
||||||
|
InotifyWatcher();
|
||||||
|
~InotifyWatcher();
|
||||||
|
|
||||||
|
InotifyWatcher(InotifyWatcher const &) = delete;
|
||||||
|
InotifyWatcher &operator=(InotifyWatcher const &) = delete;
|
||||||
|
|
||||||
|
InotifyWatcher(InotifyWatcher &&) = delete;
|
||||||
|
InotifyWatcher &operator=(InotifyWatcher &&) = delete;
|
||||||
|
|
||||||
|
void watch_path_recursively(std::filesystem::path const &path);
|
||||||
|
|
||||||
|
void set_callback(callback_t cb);
|
||||||
|
|
||||||
|
void run();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void add_watch_for_path(std::filesystem::path const &path);
|
||||||
|
void add_watch_recursively(std::filesystem::path const &path);
|
||||||
|
void remove_watch(int wd);
|
||||||
|
|
||||||
|
void dispatch_event(int wd, std::uint32_t mask, std::string_view name);
|
||||||
|
static std::filesystem::path normalize_path(
|
||||||
|
std::filesystem::path const &path);
|
||||||
|
|
||||||
|
int m_fd;
|
||||||
|
std::atomic<bool> m_running { false };
|
||||||
|
callback_t m_callback;
|
||||||
|
|
||||||
|
std::mutex m_watch_mutex;
|
||||||
|
std::unordered_map<int, std::filesystem::path> m_watch_to_path;
|
||||||
|
std::unordered_map<std::string, int> m_path_to_watch;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Waylight
|
||||||
@@ -11,8 +11,7 @@ template<class E>
|
|||||||
concept EnumLike = std::is_enum_v<E>;
|
concept EnumLike = std::is_enum_v<E>;
|
||||||
|
|
||||||
template<EnumLike E>
|
template<EnumLike E>
|
||||||
constexpr usize enum_count_v
|
constexpr usize enum_count_v = static_cast<usize>(enum_traits<E>::last)
|
||||||
= static_cast<usize>(enum_traits<E>::last)
|
|
||||||
- static_cast<usize>(enum_traits<E>::first) + 1;
|
- static_cast<usize>(enum_traits<E>::first) + 1;
|
||||||
|
|
||||||
template<EnumLike E, class T> struct enum_array {
|
template<EnumLike E, class T> struct enum_array {
|
||||||
|
|||||||
Reference in New Issue
Block a user