Skip to content

Commit

Permalink
Add Watcher implementations (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
devongovett authored Mar 29, 2019
1 parent 1eea476 commit 39fc0a6
Show file tree
Hide file tree
Showing 33 changed files with 4,039 additions and 300 deletions.
30 changes: 30 additions & 0 deletions azure-pipelines-template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
jobs:
- job: ${{ parameters.name }}
pool:
vmImage: ${{ parameters.vmImage }}
${{ if eq(parameters.name, 'Linux') }}:
container: jotadrilo/watchman:latest
strategy:
matrix:
node_8_x:
node_version: 8.x
node_10_x:
node_version: 10.x
maxParallel: 3
steps:
- task: NodeTool@0
inputs:
versionSpec: $(node_version)
displayName: 'Install Node.js'

# Install Watchman
- ${{ if eq(parameters.name, 'macOS') }}:
- script: |
brew update
brew install watchman
displayName: Install Watchman
- script: npm install
displayName: 'Install dependencies'
- script: npm test
displayName: 'Run tests'
15 changes: 15 additions & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
jobs:
- template: azure-pipelines-template.yml
parameters:
name: macOS
vmImage: macOS-10.13

- template: azure-pipelines-template.yml
parameters:
name: Linux
vmImage: ubuntu-16.04

- template: azure-pipelines-template.yml
parameters:
name: Windows
vmImage: vs2017-win2016
24 changes: 16 additions & 8 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
{
"target_name": "fschanges",
"defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ],
"sources": [ "src/FSChanges.cc" ],
"sources": [ "src/FSChanges.cc", "src/Watcher.cc", "src/Backend.cc" ],
"include_dirs" : ["<!@(node -p \"require('node-addon-api').include\")"],
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
"cflags!": ["-fexceptions"],
"cflags_cc!": ["-fexceptions"],
'cflags!': [ '-fno-exceptions' ],
'cflags_cc!': [ '-fno-exceptions' ],
"conditions": [
['OS=="mac"', {
"sources": [
"src/watchman/BSER.cc",
"src/watchman/watchman.cc",
"src/shared/brute.cc",
"src/shared/BruteForceBackend.cc",
"src/unix/fts.cc",
"src/macos/FSEvents.cc"
],
Expand All @@ -33,22 +33,30 @@
"sources": [
"src/watchman/BSER.cc",
"src/watchman/watchman.cc",
"src/shared/brute.cc",
"src/shared/BruteForceBackend.cc",
"src/linux/InotifyBackend.cc",
"src/unix/fts.cc"
],
"defines": [
"WATCHMAN",
"INOTIFY",
"BRUTE_FORCE"
]
}],
['OS=="win"', {
"sources": [
"src/shared/brute.cc",
"src/windows/win.cc"
"src/shared/BruteForceBackend.cc",
"src/windows/WindowsBackend.cc"
],
"defines": [
"WINDOWS",
"BRUTE_FORCE"
]
],
"msvs_settings": {
"VCCLCompilerTool": {
"ExceptionHandling": 1, # /EHsc
}
}
}]
]
}
Expand Down
17 changes: 16 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,19 @@ async function run() {
console.timeEnd('write');
}

run();
// run();

let fn = events => {
console.log(events);
// fschanges.unsubscribe(dir, fn, {ignore: [dir + '/.git']});
};

fschanges.subscribe(dir, fn, {ignore: [dir + '/.git']});

// let w = new Watcher(dir);
// w.getEventsSince(snapshotPath);
// w.writeSnapshot(snapshotPath);
// w.subscribe(events => {

// });
// w.unsubscribe();
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
{
"main": "build/Release/fschanges.node",
"scripts": {
"test": "mocha"
},
"dependencies": {
"node-addon-api": "^1.6.2"
},
"devDependencies": {
"fs-extra": "^7.0.1",
"mocha": "^6.0.2"
}
}
129 changes: 129 additions & 0 deletions src/Backend.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#ifdef FS_EVENTS
#include "macos/FSEvents.hh"
#endif
#ifdef WATCHMAN
#include "watchman/watchman.hh"
#endif
#ifdef WINDOWS
#include "windows/WindowsBackend.hh"
#endif
#ifdef INOTIFY
#include "linux/InotifyBackend.hh"
#endif
#include "shared/BruteForceBackend.hh"

#include "Backend.hh"
#include <unordered_map>

static std::unordered_map<std::string, std::shared_ptr<Backend>> sharedBackends;

std::shared_ptr<Backend> getBackend(std::string backend) {
// Use FSEvents on macOS by default.
// Use watchman by default if available on other platforms.
// Fall back to brute force.
#ifdef FS_EVENTS
if (backend == "fs-events" || backend == "default") {
return std::make_shared<FSEventsBackend>();
}
#endif
#ifdef WATCHMAN
if ((backend == "watchman" || backend == "default") && WatchmanBackend::checkAvailable()) {
return std::make_shared<WatchmanBackend>();
}
#endif
#ifdef WINDOWS
if (backend == "windows" || backend == "default") {
return std::make_shared<WindowsBackend>();
}
#endif
#ifdef INOTIFY
if (backend == "inotify" || backend == "default") {
return std::make_shared<InotifyBackend>();
}
#endif
if (backend == "brute-force" || backend == "default") {
return std::make_shared<BruteForceBackend>();
}

return nullptr;
}

std::shared_ptr<Backend> Backend::getShared(std::string backend) {
auto found = sharedBackends.find(backend);
if (found != sharedBackends.end()) {
return found->second;
}

auto result = getBackend(backend);
if (!result) {
return getShared("default");
}

result->run();
sharedBackends.emplace(backend, result);
return result;
}

void removeShared(Backend *backend) {
for (auto it = sharedBackends.begin(); it != sharedBackends.end(); it++) {
if (it->second.get() == backend) {
sharedBackends.erase(it);
break;
}
}
}

void Backend::run() {
mThread = std::thread([this] () {
start();
});

if (mThread.joinable()) {
mStartedSignal.wait();
}
}

void Backend::notifyStarted() {
mStartedSignal.notify();
}

void Backend::start() {
notifyStarted();
}

Backend::~Backend() {
std::unique_lock<std::mutex> lock(mMutex);

// Unwatch all subscriptions so that their state gets cleaned up
for (auto it = mSubscriptions.begin(); it != mSubscriptions.end(); it++) {
unwatch(**it);
}

// Wait for thread to stop
if (mThread.joinable()) {
mThread.join();
}
}

void Backend::watch(Watcher &watcher) {
std::unique_lock<std::mutex> lock(mMutex);
auto res = mSubscriptions.insert(&watcher);
if (res.second) {
this->subscribe(watcher);
}
}

void Backend::unwatch(Watcher &watcher) {
std::unique_lock<std::mutex> lock(mMutex);
size_t deleted = mSubscriptions.erase(&watcher);
if (deleted > 0) {
this->unsubscribe(watcher);
unref();
}
}

void Backend::unref() {
if (mSubscriptions.size() == 0) {
removeShared(this);
}
}
57 changes: 24 additions & 33 deletions src/Backend.hh
Original file line number Diff line number Diff line change
@@ -1,43 +1,34 @@
#ifndef BACKEND_H
#define BACKEND_H

#include "./Event.hh"
#include "Event.hh"
#include "Watcher.hh"
#include "Signal.hh"
#include <thread>

#ifdef FS_EVENTS
struct FSEventsBackend {
static void writeSnapshot(std::string *dir, std::string *snapshotPath, std::unordered_set<std::string> *ignore);
static EventList *getEventsSince(std::string *dir, std::string *snapshotPath, std::unordered_set<std::string> *ignore);
};
#endif
class Backend {
public:
virtual ~Backend();
void run();
void notifyStarted();

#ifdef WATCHMAN
struct WatchmanBackend {
static bool check();
static void writeSnapshot(std::string *dir, std::string *snapshotPath, std::unordered_set<std::string> *ignore);
static EventList *getEventsSince(std::string *dir, std::string *snapshotPath, std::unordered_set<std::string> *ignore);
};
#endif
virtual void start();
virtual void writeSnapshot(Watcher &watcher, std::string *snapshotPath) = 0;
virtual void getEventsSince(Watcher &watcher, std::string *snapshotPath) = 0;
virtual void subscribe(Watcher &watcher) = 0;
virtual void unsubscribe(Watcher &watcher) = 0;

struct BruteForceBackend {
static void writeSnapshot(std::string *dir, std::string *snapshotPath, std::unordered_set<std::string> *ignore);
static EventList *getEventsSince(std::string *dir, std::string *snapshotPath, std::unordered_set<std::string> *ignore);
};
static std::shared_ptr<Backend> getShared(std::string backend);

// Use FSEvents on macOS by default.
// Use watchman by default if available on other platforms.
// Fall back to brute force.
#ifdef FS_EVENTS
#define DEFAULT_BACKEND(method) FSEventsBackend::method
#elif WATCHMAN
#define DEFAULT_BACKEND(method) (WatchmanBackend::check() ? WatchmanBackend::method : BruteForceBackend::method)
#else
#define DEFAULT_BACKEND(method) BruteForceBackend::method
#endif
void watch(Watcher &watcher);
void unwatch(Watcher &watcher);
void unref();

#define GET_BACKEND(backend, method) \
(backend == "watchman" && WATCHMAN && WatchmanBackend::check() ? WatchmanBackend::method \
: backend == "fs-events" && FS_EVENTS ? FSEventsBackend::method \
: backend == "brute-force" ? BruteForceBackend::method \
: DEFAULT_BACKEND(method))
std::mutex mMutex;
std::thread mThread;
private:
std::unordered_set<Watcher *> mSubscriptions;
Signal mStartedSignal;
};

#endif
Loading

0 comments on commit 39fc0a6

Please sign in to comment.