add public file watching api

This commit is contained in:
hjfod 2023-04-27 09:22:56 +03:00
parent 8842e8f793
commit 50ff15c356
3 changed files with 106 additions and 2 deletions

View file

@ -252,4 +252,40 @@ namespace geode::utils::file {
* @param options Picker options
*/
GEODE_DLL Result<std::vector<ghc::filesystem::path>> pickFiles(FilePickOptions const& options);
class GEODE_DLL FileWatchEvent : public Event {
protected:
ghc::filesystem::path m_path;
public:
FileWatchEvent(ghc::filesystem::path const& path);
ghc::filesystem::path getPath() const;
};
class GEODE_DLL FileWatchFilter : public EventFilter<FileWatchEvent> {
protected:
ghc::filesystem::path m_path;
public:
using Callback = void(FileWatchEvent*);
ListenerResult handle(utils::MiniFunction<Callback> callback, FileWatchEvent* event);
FileWatchFilter(ghc::filesystem::path const& path);
};
/**
* Watch a file for changes. Whenever the file is modified on disk, a
* FileWatchEvent is emitted. Add an EventListener with FileWatchFilter
* to catch these events
* @param file The file to watch
* @note Watching uses file system equivalence instead of path equivalence,
* so different paths that point to the same file will be considered the
* same
*/
GEODE_DLL Result<> watchFile(ghc::filesystem::path const& file);
/**
* Stop watching a file for changes
* @param file The file to unwatch
*/
GEODE_DLL void unwatchFile(ghc::filesystem::path const& file);
}

View file

@ -25,12 +25,28 @@ protected:
public:
bool watching() const;
ghc::filesystem::path path() {
ghc::filesystem::path path() const {
return m_file;
}
FileWatcher(
ghc::filesystem::path const& file, FileWatchCallback callback, ErrorCallback error = nullptr
ghc::filesystem::path const& file,
FileWatchCallback callback,
ErrorCallback error = nullptr
);
FileWatcher(FileWatcher const&) = delete;
inline FileWatcher(FileWatcher&& other)
: m_file(std::move(other.m_file)),
m_callback(std::move(other.m_callback)),
m_error(std::move(other.m_error)),
m_filemode(other.m_filemode),
m_platformHandle(other.m_platformHandle),
m_exiting(other.m_exiting)
{
other.m_callback = nullptr;
other.m_error = nullptr;
other.m_platformHandle = nullptr;
other.m_exiting = true;
}
~FileWatcher();
};

View file

@ -11,6 +11,7 @@
#include <mz_strm_os.h>
#include <mz_strm_mem.h>
#include <mz_zip.h>
#include <internal/FileWatcher.hpp>
using namespace geode::prelude;
using namespace geode::utils::file;
@ -551,3 +552,54 @@ Result<> Zip::addAllFrom(Path const& dir) {
Result<> Zip::addFolder(Path const& entry) {
return m_impl->addFolder(entry);
}
FileWatchEvent::FileWatchEvent(ghc::filesystem::path const& path)
: m_path(path) {}
ghc::filesystem::path FileWatchEvent::getPath() const {
return m_path;
}
ListenerResult FileWatchFilter::handle(
MiniFunction<Callback> callback,
FileWatchEvent* event
) {
std::error_code ec;
if (ghc::filesystem::equivalent(event->getPath(), m_path, ec)) {
callback(event);
}
return ListenerResult::Propagate;
}
FileWatchFilter::FileWatchFilter(ghc::filesystem::path const& path)
: m_path(path) {}
// This is a vector because need to use ghc::filesystem::equivalent for
// comparisons and removal is not exactly performance-critical here
// (who's going to add and remove 500 file watchers every frame)
static std::vector<std::unique_ptr<FileWatcher>> FILE_WATCHERS {};
Result<> file::watchFile(ghc::filesystem::path const& file) {
if (!ghc::filesystem::exists(file)) {
return Err("File does not exist");
}
auto watcher = std::make_unique<FileWatcher>(
file,
[](auto const& path) {
Loader::get()->queueInGDThread([=] {
FileWatchEvent(path).post();
});
}
);
if (!watcher->watching()) {
return Err("Unknown error watching file");
}
FILE_WATCHERS.emplace_back(std::move(watcher));
return Ok();
}
void file::unwatchFile(ghc::filesystem::path const& file) {
ranges::remove(FILE_WATCHERS, [=](std::unique_ptr<FileWatcher> const& watcher) {
return ghc::filesystem::equivalent(file, watcher->path());
});
}