#include "Watcher.hh" #include using namespace Napi; struct WatcherHash { std::size_t operator() (std::shared_ptr const &k) const { return std::hash()(k->mDir); } }; struct WatcherCompare { size_t operator() (std::shared_ptr const &a, std::shared_ptr const &b) const { return *a == *b; } }; static std::unordered_set, WatcherHash, WatcherCompare> sharedWatchers; std::shared_ptr Watcher::getShared(std::string dir, std::unordered_set ignore) { std::shared_ptr watcher = std::make_shared(dir, ignore); auto found = sharedWatchers.find(watcher); if (found != sharedWatchers.end()) { return *found; } sharedWatchers.insert(watcher); return watcher; } void removeShared(Watcher *watcher) { for (auto it = sharedWatchers.begin(); it != sharedWatchers.end(); it++) { if (it->get() == watcher) { sharedWatchers.erase(it); break; } } } Watcher::Watcher(std::string dir, std::unordered_set ignore) : mDir(dir), mIgnore(ignore), mWatched(false), mAsync(NULL), mCallingCallbacks(false) { mDebounce = Debounce::getShared(); mDebounce->add(this, [this] () { triggerCallbacks(); }); } Watcher::~Watcher() { mDebounce->remove(this); } void Watcher::wait() { std::unique_lock lk(mMutex); mCond.wait(lk); } void Watcher::notify() { std::unique_lock lk(mMutex); mCond.notify_all(); if (mCallbacks.size() > 0 && mEvents.size() > 0) { mDebounce->trigger(); } } void Watcher::notifyError(std::exception &err) { std::unique_lock lk(mMutex); if (mCallingCallbacks) { mCallbackSignal.wait(); mCallbackSignal.reset(); } mError = err.what(); triggerCallbacks(); } void Watcher::triggerCallbacks() { std::lock_guard l(mCallbackEventsMutex); if (mCallbacks.size() > 0 && (mEvents.size() > 0 || mError.size() > 0)) { if (mCallingCallbacks) { mCallbackSignal.wait(); mCallbackSignal.reset(); } mCallbackEvents = mEvents.getEvents(); mEvents.clear(); uv_async_send(mAsync); } } Value Watcher::callbackEventsToJS(const Env& env) { std::lock_guard l(mCallbackEventsMutex); EscapableHandleScope scope(env); Array arr = Array::New(env, mCallbackEvents.size()); size_t currentEventIndex = 0; for (auto eventIterator = mCallbackEvents.begin(); eventIterator != mCallbackEvents.end(); eventIterator++) { arr.Set(currentEventIndex++, eventIterator->toJS(env)); } return scope.Escape(arr); } // TODO: Doesn't this need some kind of locking? void Watcher::clearCallbacks() { mCallbacks.clear(); } void Watcher::fireCallbacks(uv_async_t *handle) { Watcher *watcher = (Watcher *)handle->data; watcher->mCallingCallbacks = true; watcher->mCallbacksIterator = watcher->mCallbacks.begin(); while (watcher->mCallbacksIterator != watcher->mCallbacks.end()) { auto it = watcher->mCallbacksIterator; HandleScope scope(it->Env()); auto err = watcher->mError.size() > 0 ? Error::New(it->Env(), watcher->mError).Value() : it->Env().Null(); auto events = watcher->callbackEventsToJS(it->Env()); it->MakeCallback(it->Env().Global(), std::initializer_list{err, events}); // Throw errors from the callback as fatal exceptions // If we don't handle these node segfaults... if (it->Env().IsExceptionPending()) { Napi::Error err = it->Env().GetAndClearPendingException(); napi_fatal_exception(it->Env(), err.Value()); } // If the iterator was changed, then the callback trigged an unwatch. // The iterator will have been set to the next valid callback. // If it is the same as before, increment it. if (watcher->mCallbacksIterator == it) { watcher->mCallbacksIterator++; } } watcher->mCallingCallbacks = false; if (watcher->mError.size() > 0) { watcher->clearCallbacks(); } if (watcher->mCallbacks.size() == 0) { watcher->unref(); } else { watcher->mCallbackSignal.notify(); } } bool Watcher::watch(FunctionReference callback) { std::unique_lock lk(mMutex); auto res = mCallbacks.insert(std::move(callback)); if (res.second && !mWatched) { mAsync = new uv_async_t; mAsync->data = (void *)this; uv_async_init(uv_default_loop(), mAsync, Watcher::fireCallbacks); mWatched = true; return true; } return false; } bool Watcher::unwatch(Function callback) { std::unique_lock lk(mMutex); bool removed = false; for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) { if (it->Value() == callback) { mCallbacksIterator = mCallbacks.erase(it); removed = true; break; } } if (removed && mCallbacks.size() == 0) { unref(); return true; } return false; } void Watcher::unref() { if (mCallbacks.size() == 0 && !mCallingCallbacks) { if (mWatched) { mWatched = false; uv_close((uv_handle_t *)mAsync, Watcher::onClose); } removeShared(this); } } void Watcher::onClose(uv_handle_t *handle) { delete (uv_async_t *)handle; } bool Watcher::isIgnored(std::string path) { for (auto it = mIgnore.begin(); it != mIgnore.end(); it++) { auto dir = *it + DIR_SEP; if (*it == path || path.compare(0, dir.size(), dir) == 0) { return true; } } return false; }