[0c6b92a] | 1 | #include "Watcher.hh"
|
---|
| 2 | #include <unordered_set>
|
---|
| 3 |
|
---|
| 4 | using namespace Napi;
|
---|
| 5 |
|
---|
| 6 | struct WatcherHash {
|
---|
| 7 | std::size_t operator() (WatcherRef const &k) const {
|
---|
| 8 | return std::hash<std::string>()(k->mDir);
|
---|
| 9 | }
|
---|
| 10 | };
|
---|
| 11 |
|
---|
| 12 | struct WatcherCompare {
|
---|
| 13 | size_t operator() (WatcherRef const &a, WatcherRef const &b) const {
|
---|
| 14 | return *a == *b;
|
---|
| 15 | }
|
---|
| 16 | };
|
---|
| 17 |
|
---|
| 18 | static std::unordered_set<WatcherRef , WatcherHash, WatcherCompare> sharedWatchers;
|
---|
| 19 |
|
---|
| 20 | WatcherRef Watcher::getShared(std::string dir, std::unordered_set<std::string> ignorePaths, std::unordered_set<Glob> ignoreGlobs) {
|
---|
| 21 | WatcherRef watcher = std::make_shared<Watcher>(dir, ignorePaths, ignoreGlobs);
|
---|
| 22 | auto found = sharedWatchers.find(watcher);
|
---|
| 23 | if (found != sharedWatchers.end()) {
|
---|
| 24 | return *found;
|
---|
| 25 | }
|
---|
| 26 |
|
---|
| 27 | sharedWatchers.insert(watcher);
|
---|
| 28 | return watcher;
|
---|
| 29 | }
|
---|
| 30 |
|
---|
| 31 | void removeShared(Watcher *watcher) {
|
---|
| 32 | for (auto it = sharedWatchers.begin(); it != sharedWatchers.end(); it++) {
|
---|
| 33 | if (it->get() == watcher) {
|
---|
| 34 | sharedWatchers.erase(it);
|
---|
| 35 | break;
|
---|
| 36 | }
|
---|
| 37 | }
|
---|
| 38 |
|
---|
| 39 | // Free up memory.
|
---|
| 40 | if (sharedWatchers.size() == 0) {
|
---|
| 41 | sharedWatchers.rehash(0);
|
---|
| 42 | }
|
---|
| 43 | }
|
---|
| 44 |
|
---|
| 45 | Watcher::Watcher(std::string dir, std::unordered_set<std::string> ignorePaths, std::unordered_set<Glob> ignoreGlobs)
|
---|
| 46 | : mDir(dir),
|
---|
| 47 | mIgnorePaths(ignorePaths),
|
---|
| 48 | mIgnoreGlobs(ignoreGlobs) {
|
---|
| 49 | mDebounce = Debounce::getShared();
|
---|
| 50 | mDebounce->add(this, [this] () {
|
---|
| 51 | triggerCallbacks();
|
---|
| 52 | });
|
---|
| 53 | }
|
---|
| 54 |
|
---|
| 55 | Watcher::~Watcher() {
|
---|
| 56 | mDebounce->remove(this);
|
---|
| 57 | }
|
---|
| 58 |
|
---|
| 59 | void Watcher::wait() {
|
---|
| 60 | std::unique_lock<std::mutex> lk(mMutex);
|
---|
| 61 | mCond.wait(lk);
|
---|
| 62 | }
|
---|
| 63 |
|
---|
| 64 | void Watcher::notify() {
|
---|
| 65 | std::unique_lock<std::mutex> lk(mMutex);
|
---|
| 66 | mCond.notify_all();
|
---|
| 67 |
|
---|
| 68 | if (mCallbacks.size() > 0 && mEvents.size() > 0) {
|
---|
| 69 | // We must release our lock before calling into the debouncer
|
---|
| 70 | // to avoid a deadlock: the debouncer thread itself will require
|
---|
| 71 | // our lock from its thread when calling into `triggerCallbacks`
|
---|
| 72 | // while holding its own debouncer lock.
|
---|
| 73 | lk.unlock();
|
---|
| 74 | mDebounce->trigger();
|
---|
| 75 | }
|
---|
| 76 | }
|
---|
| 77 |
|
---|
| 78 | struct CallbackData {
|
---|
| 79 | std::string error;
|
---|
| 80 | std::vector<Event> events;
|
---|
| 81 | CallbackData(std::string error, std::vector<Event> events) : error(error), events(events) {}
|
---|
| 82 | };
|
---|
| 83 |
|
---|
| 84 | Value callbackEventsToJS(const Env &env, std::vector<Event> &events) {
|
---|
| 85 | EscapableHandleScope scope(env);
|
---|
| 86 | Array arr = Array::New(env, events.size());
|
---|
| 87 | size_t currentEventIndex = 0;
|
---|
| 88 | for (auto eventIterator = events.begin(); eventIterator != events.end(); eventIterator++) {
|
---|
| 89 | arr.Set(currentEventIndex++, eventIterator->toJS(env));
|
---|
| 90 | }
|
---|
| 91 | return scope.Escape(arr);
|
---|
| 92 | }
|
---|
| 93 |
|
---|
| 94 | void callJSFunction(Napi::Env env, Function jsCallback, CallbackData *data) {
|
---|
| 95 | HandleScope scope(env);
|
---|
| 96 | auto err = data->error.size() > 0 ? Error::New(env, data->error).Value() : env.Null();
|
---|
| 97 | auto events = callbackEventsToJS(env, data->events);
|
---|
| 98 | jsCallback.Call({err, events});
|
---|
| 99 | delete data;
|
---|
| 100 |
|
---|
| 101 | // Throw errors from the callback as fatal exceptions
|
---|
| 102 | // If we don't handle these node segfaults...
|
---|
| 103 | if (env.IsExceptionPending()) {
|
---|
| 104 | Napi::Error err = env.GetAndClearPendingException();
|
---|
| 105 | napi_fatal_exception(env, err.Value());
|
---|
| 106 | }
|
---|
| 107 | }
|
---|
| 108 |
|
---|
| 109 | void Watcher::notifyError(std::exception &err) {
|
---|
| 110 | std::unique_lock<std::mutex> lk(mMutex);
|
---|
| 111 | for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
|
---|
| 112 | CallbackData *data = new CallbackData(err.what(), {});
|
---|
| 113 | it->tsfn.BlockingCall(data, callJSFunction);
|
---|
| 114 | }
|
---|
| 115 |
|
---|
| 116 | clearCallbacks();
|
---|
| 117 | }
|
---|
| 118 |
|
---|
| 119 | // This function is called from the debounce thread.
|
---|
| 120 | void Watcher::triggerCallbacks() {
|
---|
| 121 | std::unique_lock<std::mutex> lk(mMutex);
|
---|
| 122 | if (mCallbacks.size() > 0 && mEvents.size() > 0) {
|
---|
| 123 | auto events = mEvents.getEvents();
|
---|
| 124 | mEvents.clear();
|
---|
| 125 |
|
---|
| 126 | for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
|
---|
| 127 | it->tsfn.BlockingCall(new CallbackData("", events), callJSFunction);
|
---|
| 128 | }
|
---|
| 129 | }
|
---|
| 130 | }
|
---|
| 131 |
|
---|
| 132 | // This should be called from the JavaScript thread.
|
---|
| 133 | bool Watcher::watch(Function callback) {
|
---|
| 134 | std::unique_lock<std::mutex> lk(mMutex);
|
---|
| 135 |
|
---|
| 136 | auto it = findCallback(callback);
|
---|
| 137 | if (it != mCallbacks.end()) {
|
---|
| 138 | return false;
|
---|
| 139 | }
|
---|
| 140 |
|
---|
| 141 | auto tsfn = ThreadSafeFunction::New(
|
---|
| 142 | callback.Env(),
|
---|
| 143 | callback,
|
---|
| 144 | "Watcher callback",
|
---|
| 145 | 0, // Unlimited queue
|
---|
| 146 | 1 // Initial thread count
|
---|
| 147 | );
|
---|
| 148 |
|
---|
| 149 | mCallbacks.push_back(Callback {
|
---|
| 150 | tsfn,
|
---|
| 151 | Napi::Persistent(callback),
|
---|
| 152 | std::this_thread::get_id()
|
---|
| 153 | });
|
---|
| 154 |
|
---|
| 155 | return true;
|
---|
| 156 | }
|
---|
| 157 |
|
---|
| 158 | // This should be called from the JavaScript thread.
|
---|
| 159 | std::vector<Callback>::iterator Watcher::findCallback(Function callback) {
|
---|
| 160 | for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
|
---|
| 161 | // Only consider callbacks created by the same thread, or V8 will panic.
|
---|
| 162 | if (it->threadId == std::this_thread::get_id() && it->ref.Value() == callback) {
|
---|
| 163 | return it;
|
---|
| 164 | }
|
---|
| 165 | }
|
---|
| 166 |
|
---|
| 167 | return mCallbacks.end();
|
---|
| 168 | }
|
---|
| 169 |
|
---|
| 170 | // This should be called from the JavaScript thread.
|
---|
| 171 | bool Watcher::unwatch(Function callback) {
|
---|
| 172 | std::unique_lock<std::mutex> lk(mMutex);
|
---|
| 173 |
|
---|
| 174 | bool removed = false;
|
---|
| 175 | auto it = findCallback(callback);
|
---|
| 176 | if (it != mCallbacks.end()) {
|
---|
| 177 | it->tsfn.Release();
|
---|
| 178 | it->ref.Unref();
|
---|
| 179 | mCallbacks.erase(it);
|
---|
| 180 | removed = true;
|
---|
| 181 | }
|
---|
| 182 |
|
---|
| 183 | if (removed && mCallbacks.size() == 0) {
|
---|
| 184 | unref();
|
---|
| 185 | return true;
|
---|
| 186 | }
|
---|
| 187 |
|
---|
| 188 | return false;
|
---|
| 189 | }
|
---|
| 190 |
|
---|
| 191 | void Watcher::unref() {
|
---|
| 192 | if (mCallbacks.size() == 0) {
|
---|
| 193 | removeShared(this);
|
---|
| 194 | }
|
---|
| 195 | }
|
---|
| 196 |
|
---|
| 197 | void Watcher::destroy() {
|
---|
| 198 | std::unique_lock<std::mutex> lk(mMutex);
|
---|
| 199 | clearCallbacks();
|
---|
| 200 | }
|
---|
| 201 |
|
---|
| 202 | // Private because it doesn't lock.
|
---|
| 203 | void Watcher::clearCallbacks() {
|
---|
| 204 | for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
|
---|
| 205 | it->tsfn.Release();
|
---|
| 206 | it->ref.Unref();
|
---|
| 207 | }
|
---|
| 208 |
|
---|
| 209 | mCallbacks.clear();
|
---|
| 210 | unref();
|
---|
| 211 | }
|
---|
| 212 |
|
---|
| 213 | bool Watcher::isIgnored(std::string path) {
|
---|
| 214 | for (auto it = mIgnorePaths.begin(); it != mIgnorePaths.end(); it++) {
|
---|
| 215 | auto dir = *it + DIR_SEP;
|
---|
| 216 | if (*it == path || path.compare(0, dir.size(), dir) == 0) {
|
---|
| 217 | return true;
|
---|
| 218 | }
|
---|
| 219 | }
|
---|
| 220 |
|
---|
| 221 | auto basePath = mDir + DIR_SEP;
|
---|
| 222 |
|
---|
| 223 | if (path.rfind(basePath, 0) != 0) {
|
---|
| 224 | return false;
|
---|
| 225 | }
|
---|
| 226 |
|
---|
| 227 | auto relativePath = path.substr(basePath.size());
|
---|
| 228 |
|
---|
| 229 | for (auto it = mIgnoreGlobs.begin(); it != mIgnoreGlobs.end(); it++) {
|
---|
| 230 | if (it->isIgnored(relativePath)) {
|
---|
| 231 | return true;
|
---|
| 232 | }
|
---|
| 233 | }
|
---|
| 234 |
|
---|
| 235 | return false;
|
---|
| 236 | }
|
---|