feat(watch): add portable directory watching and timing
This commit is contained in:
@@ -8,6 +8,8 @@ add_library(iZo::izo ALIAS izo)
|
|||||||
target_sources(izo
|
target_sources(izo
|
||||||
PRIVATE
|
PRIVATE
|
||||||
src/dialogs.cpp
|
src/dialogs.cpp
|
||||||
|
src/directory_watcher.cpp
|
||||||
|
src/time.cpp
|
||||||
$<$<PLATFORM_ID:Windows>:src/dialogs_windows.cpp>
|
$<$<PLATFORM_ID:Windows>:src/dialogs_windows.cpp>
|
||||||
$<$<PLATFORM_ID:Linux>:src/dialogs_linux.cpp>
|
$<$<PLATFORM_ID:Linux>:src/dialogs_linux.cpp>
|
||||||
)
|
)
|
||||||
|
|||||||
96
src/directory_watcher.cpp
Normal file
96
src/directory_watcher.cpp
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
#include <izo/directory_watcher.hpp>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <map>
|
||||||
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace izo {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using snapshot = std::map<std::filesystem::path, std::filesystem::file_time_type>;
|
||||||
|
|
||||||
|
snapshot scan(const directory_watch_options& options) {
|
||||||
|
snapshot files;
|
||||||
|
std::error_code ec;
|
||||||
|
auto add = [&](const std::filesystem::directory_entry& entry) {
|
||||||
|
if (!entry.is_regular_file(ec) || ec) { ec.clear(); return; }
|
||||||
|
const auto time = entry.last_write_time(ec);
|
||||||
|
if (!ec) files.emplace(entry.path(), time);
|
||||||
|
ec.clear();
|
||||||
|
};
|
||||||
|
if (options.recursive) {
|
||||||
|
for (std::filesystem::recursive_directory_iterator it(
|
||||||
|
options.path, std::filesystem::directory_options::skip_permission_denied, ec), end;
|
||||||
|
!ec && it != end; it.increment(ec)) add(*it);
|
||||||
|
} else {
|
||||||
|
for (std::filesystem::directory_iterator it(
|
||||||
|
options.path, std::filesystem::directory_options::skip_permission_denied, ec), end;
|
||||||
|
!ec && it != end; it.increment(ec)) add(*it);
|
||||||
|
}
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
struct directory_watcher::implementation {
|
||||||
|
directory_watch_options options;
|
||||||
|
file_event_callback callback;
|
||||||
|
std::atomic<bool> running{true};
|
||||||
|
std::thread worker;
|
||||||
|
|
||||||
|
implementation(directory_watch_options value, file_event_callback fn)
|
||||||
|
: options(std::move(value)), callback(std::move(fn)) {
|
||||||
|
worker = std::thread([this] {
|
||||||
|
auto previous = scan(options);
|
||||||
|
while (running.load(std::memory_order_relaxed)) {
|
||||||
|
std::this_thread::sleep_for(options.poll_interval);
|
||||||
|
if (!running.load(std::memory_order_relaxed)) break;
|
||||||
|
auto current = scan(options);
|
||||||
|
for (const auto& item : current) {
|
||||||
|
const auto old = previous.find(item.first);
|
||||||
|
if (old == previous.end()) callback({file_change::added, item.first});
|
||||||
|
else if (old->second != item.second) callback({file_change::modified, item.first});
|
||||||
|
}
|
||||||
|
for (const auto& item : previous) {
|
||||||
|
if (current.find(item.first) == current.end())
|
||||||
|
callback({file_change::removed, item.first});
|
||||||
|
}
|
||||||
|
previous = std::move(current);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
~implementation() {
|
||||||
|
running.store(false, std::memory_order_relaxed);
|
||||||
|
if (worker.joinable()) worker.join();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
directory_watcher::directory_watcher(std::unique_ptr<implementation> implementation) noexcept
|
||||||
|
: implementation_(std::move(implementation)) {}
|
||||||
|
directory_watcher::~directory_watcher() = default;
|
||||||
|
directory_watcher::directory_watcher(directory_watcher&&) noexcept = default;
|
||||||
|
directory_watcher& directory_watcher::operator=(directory_watcher&&) noexcept = default;
|
||||||
|
void directory_watcher::stop() noexcept { implementation_.reset(); }
|
||||||
|
|
||||||
|
directory_watcher watch_directory(const directory_watch_options& options,
|
||||||
|
file_event_callback callback, std::string* error_message) {
|
||||||
|
std::error_code ec;
|
||||||
|
if (!std::filesystem::is_directory(options.path, ec)) {
|
||||||
|
if (error_message) *error_message = ec ? ec.message() : "Watch path is not a directory";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (!callback) {
|
||||||
|
if (error_message) *error_message = "A file event callback is required";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (options.poll_interval <= std::chrono::milliseconds::zero()) {
|
||||||
|
if (error_message) *error_message = "Poll interval must be positive";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return directory_watcher(std::make_unique<directory_watcher::implementation>(options,
|
||||||
|
std::move(callback)));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace izo
|
||||||
13
src/time.cpp
Normal file
13
src/time.cpp
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#include <izo/time.hpp>
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace izo {
|
||||||
|
|
||||||
|
monotonic_clock::time_point monotonic_now() noexcept { return monotonic_clock::now(); }
|
||||||
|
|
||||||
|
void sleep_for(std::chrono::nanoseconds duration) {
|
||||||
|
if (duration > duration.zero()) std::this_thread::sleep_for(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace izo
|
||||||
Reference in New Issue
Block a user