Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

linux: support simple ignore patterns #124

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"sources": [
"src/NSFW.cpp",
"src/Queue.cpp",
"src/NativeInterface.cpp"
"src/NativeInterface.cpp",
"src/PathFilter.cpp"
],
"include_dirs": [
"includes",
Expand Down Expand Up @@ -102,7 +103,7 @@
],
"libraries": [
"-L/usr/local/lib",
"-linotify"
"-linotify"
]
}],
]
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const nsfw = require('nsfw');
- **options**
- **debounceMS**: time in milliseconds to debounce the event callback
- **errorCallback**: callback to fire in the case of errors
- **ignoreGlobs**: glob-like patterns to ignore paths while watching


Returns a Promise that resolves to the created NSFW Object.
Expand Down
2 changes: 2 additions & 0 deletions includes/NSFW.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include "./Queue.h"
#include "./NativeInterface.h"
#include "./PathFilter.h"

class NSFW : public Napi::ObjectWrap<NSFW> {
private:
Expand All @@ -23,6 +24,7 @@ class NSFW : public Napi::ObjectWrap<NSFW> {
std::unique_ptr<NativeInterface> mInterface;
std::mutex mInterfaceLock;
std::shared_ptr<EventQueue> mQueue;
std::shared_ptr<PathFilter> mPathFilter;
std::string mPath;
std::thread mPollThread;
std::atomic<bool> mRunning;
Expand Down
4 changes: 3 additions & 1 deletion includes/NativeInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ using NativeImplementation = InotifyService;
#endif

#include "Queue.h"
#include "PathFilter.h"
#include <vector>

class NativeInterface {
public:
NativeInterface(const std::string &path, std::shared_ptr<EventQueue> queue);
NativeInterface(const std::string &path, std::shared_ptr<EventQueue> queue, std::shared_ptr<PathFilter> pathFilter);
~NativeInterface();

std::string getError();
Expand All @@ -26,6 +27,7 @@ class NativeInterface {

private:
std::unique_ptr<NativeImplementation> mNativeInterface;
std::shared_ptr<PathFilter> mPathFilter;
};

#endif
23 changes: 23 additions & 0 deletions includes/PathFilter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#ifndef FILTER_H
#define FILTER_H

#include <string>
#include <vector>

class PathFilter {
private:
std::string mRoot;
std::vector<std::string> mRootFilters;
std::vector<std::string> mFilters;

public:
PathFilter(const std::string &root);
~PathFilter();

/** register a glob filter */
void addIgnoreGlob(const std::string &glob);
/** return true if path should be ignored, false otherwise */
bool isIgnored(const std::string &path);
};

#endif
3 changes: 3 additions & 0 deletions includes/Queue.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#ifndef NSFW_QUEUE_H
#define NSFW_QUEUE_H

#include "./PathFilter.h"

#include <string>
#include <memory>
#include <deque>
Expand Down Expand Up @@ -40,6 +42,7 @@ class EventQueue {
std::size_t count();
std::unique_ptr<Event> dequeue();
std::unique_ptr<std::vector<std::unique_ptr<Event>>> dequeueAll();
std::unique_ptr<std::vector<std::unique_ptr<Event>>> dequeueAll(const std::shared_ptr<PathFilter> &pathFilter);
void enqueue(
const EventType type,
const std::string &fromDirectory,
Expand Down
3 changes: 2 additions & 1 deletion includes/linux/InotifyService.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "InotifyEventLoop.h"
#include "InotifyTree.h"
#include "../Queue.h"
#include "../PathFilter.h"
#include <queue>
#include <map>

Expand All @@ -12,7 +13,7 @@ class InotifyTree;

class InotifyService {
public:
InotifyService(std::shared_ptr<EventQueue> queue, std::string path);
InotifyService(std::shared_ptr<EventQueue> queue, std::string path, std::shared_ptr<PathFilter> pathFilter);

std::string getError();
bool hasErrored();
Expand Down
8 changes: 7 additions & 1 deletion includes/linux/InotifyTree.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@
#include <sstream>
#include <vector>
#include <map>
#include <memory>
#include <unordered_set>

#include "../PathFilter.h"

class InotifyTree {
public:
InotifyTree(int inotifyInstance, std::string path);
InotifyTree(int inotifyInstance, std::string path, std::shared_ptr<PathFilter> pathFilter);

void addDirectory(int wd, std::string name);
std::string getError();
Expand All @@ -31,6 +34,7 @@ class InotifyTree {
InotifyNode(
InotifyTree *tree,
int inotifyInstance,
std::shared_ptr<PathFilter> pathFilter,
InotifyNode *parent,
std::string directory,
std::string name,
Expand Down Expand Up @@ -68,6 +72,7 @@ class InotifyTree {
std::string mFullPath;
ino_t mInodeNumber;
const int mInotifyInstance;
std::shared_ptr<PathFilter> mPathFilter;
std::string mName;
InotifyNode *mParent;
InotifyTree *mTree;
Expand All @@ -83,6 +88,7 @@ class InotifyTree {

std::string mError;
const int mInotifyInstance;
std::shared_ptr<PathFilter> mPathFilter;
std::map<int, InotifyNode *> *mInotifyNodeByWatchDescriptor;
std::unordered_set<ino_t> inodes;
InotifyNode *mRoot;
Expand Down
3 changes: 2 additions & 1 deletion includes/osx/FSEventsService.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "RunLoop.h"
#include "../Queue.h"
#include "../PathFilter.h"

#include <CoreServices/CoreServices.h>
#include <time.h>
Expand All @@ -24,7 +25,7 @@ class RunLoop;

class FSEventsService {
public:
FSEventsService(std::shared_ptr<EventQueue> queue, std::string path);
FSEventsService(std::shared_ptr<EventQueue> queue, std::string path, std::shared_ptr<PathFilter> pathFilter);

friend void FSEventsServiceCallback(
ConstFSEventStreamRef streamRef,
Expand Down
3 changes: 2 additions & 1 deletion includes/win32/Controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
#include <string>
#include <memory>
#include "Watcher.h"
#include "../PathFilter.h"

class EventQueue;

class Controller {
public:
Controller(std::shared_ptr<EventQueue> queue, const std::string &path);
Controller(std::shared_ptr<EventQueue> queue, const std::string &path, std::shared_ptr<PathFilter> pathFilter);

std::string getError();
bool hasErrored();
Expand Down
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,7 @@ declare module 'nsfw' {
debounceMS?: number;
/** callback to fire in the case of errors */
errorCallback: (err: any) => void
/** glob-like patterns to ignore paths while watching */
ignoreGlobs: string[];
}
}
72 changes: 72 additions & 0 deletions js/spec/index-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,78 @@ describe('Node Sentinel File Watcher', function() {
watch = null;
}
});

if (process.platform === 'linux') {
it('can cut part of the watch tree (linux)', async function () {
// Creates `${name}/test.file`
async function mkdir(name) {
const folder = path.resolve(workDir, name);
await fse.mkdir(folder, { recursive: true });
await fse.writeFile(path.resolve(folder, 'test.file'), name);
}
// `workDir/aaa`
const aaa = path.resolve(workDir, 'aaa'); // ignored
const aaab = path.resolve(workDir, 'aaab'); // not ignored
// `workDir/bb*`
const baa = path.resolve(workDir, 'baa'); // not ignored
const bbb = path.resolve(workDir, 'bbb'); // ignored
const bbc = path.resolve(workDir, 'bbc'); // ignored
// `*zz`
const czz = path.resolve(workDir, 'czz'); // ignored
const dzza = path.resolve(workDir, 'dzza'); // not ignored
// `**/eee`
const eee = path.resolve(workDir, 'eee'); // ignored
const feee = path.resolve(workDir, 'feee'); // not ignored
// `**/gaa///`
const gaa = path.resolve(workDir, 'gaa'); // ignored
const gaab = path.resolve(workDir, 'gaab'); // not ignored
const folders = [aaa, aaab, baa, bbb, bbc, czz, dzza, eee, feee, gaa, gaab];
await Promise.all(folders.map(mkdir));
const expectIgnored = new Set([bbb, bbc, czz, eee, gaa]);
const expectNotIgnored = new Set([aaab, baa, dzza, feee, gaab]);
const expectWorkDirEvents = new Set(Array.from(expectNotIgnored, folder => path.basename(folder)));
const actualIgnored = new Set(); // should be empty
const actualNotIgnored = new Set(); // should be == expectNotIgnored
const actualWorkDirEvents = new Set(); // should be == expectWorkDirEvents
function findEvent(element) {
if (element.action === nsfw.actions.DELETED)
{
if (element.directory === workDir && expectWorkDirEvents.has(element.file)) {
actualWorkDirEvents.add(element.file); // Ok.
} else if (element.file === 'test.file') {
if (expectIgnored.has(element.directory)) {
actualIgnored.add(element.directory); // This should fail the test.
} else if (expectNotIgnored.has(element.directory)) {
actualNotIgnored.add(element.directory); // Ok.
}
}
}
}
let watch = await nsfw(
workDir,
events => events.forEach(findEvent),
{ debounceMS: DEBOUNCE, ignoreGlobs: [
workDir + '/aaa',
workDir + '/bb*',
'*zz',
'**/eee',
'**/gaa///',
] }
);
try {
await watch.start();
await sleep(TIMEOUT_PER_STEP);
await Promise.all(folders.map(folder => fse.remove(folder)));
await sleep(TIMEOUT_PER_STEP);
} finally {
await watch.stop();
watch = null;
}
assert.deepStrictEqual(new Set(), actualIgnored, 'some folders were not ignored');
assert.deepStrictEqual(expectNotIgnored, actualNotIgnored, 'some folders got ignored');
assert.deepStrictEqual(expectWorkDirEvents, actualWorkDirEvents);
});
}
});

describe('Pausing', function () {
Expand Down
8 changes: 6 additions & 2 deletions js/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,11 @@ function NSFWFilePoller(watchPath, eventCallback, debounceMS) {
}


const buildNSFW = async (watchPath, eventCallback, { debounceMS = 500, errorCallback: _errorCallback } = {}) => {
const buildNSFW = async (watchPath, eventCallback, {
debounceMS = 500,
errorCallback: _errorCallback,
ignoreGlobs
} = {}) => {
if (Number.isInteger(debounceMS)) {
if (debounceMS < 1) {
throw new Error('Minimum debounce is 1ms.');
Expand All @@ -74,7 +78,7 @@ const buildNSFW = async (watchPath, eventCallback, { debounceMS = 500, errorCall
}

if (stats.isDirectory()) {
return new NSFW(watchPath, eventCallback, { debounceMS, errorCallback });
return new NSFW(watchPath, eventCallback, { debounceMS, errorCallback, ignoreGlobs });
} else if (stats.isFile()) {
return new NSFWFilePoller(watchPath, eventCallback, debounceMS);
} else {
Expand Down
23 changes: 21 additions & 2 deletions src/NSFW.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "../includes/NSFW.h"
#include <memory>

Napi::FunctionReference NSFW::constructor;
std::size_t NSFW::instanceCount = 0;
Expand All @@ -9,6 +10,7 @@ NSFW::NSFW(const Napi::CallbackInfo &info):
mDebounceMS(0),
mInterface(nullptr),
mQueue(std::make_shared<EventQueue>()),
mPathFilter(nullptr),
mPath(""),
mRunning(false)
{
Expand All @@ -22,6 +24,7 @@ NSFW::NSFW(const Napi::CallbackInfo &info):
}

mPath = info[0].ToString();
mPathFilter = std::make_shared<PathFilter>(mPath);

if (info.Length() < 2 || !info[1].IsFunction()) {
throw Napi::TypeError::New(env, "Must pass an event callback as the second parameter to NSFW.");
Expand Down Expand Up @@ -70,6 +73,22 @@ NSFW::NSFW(const Napi::CallbackInfo &info):
0,
1
);

Napi::Value maybeIgnoreGlobs = options["ignoreGlobs"];
if (options.Has("ignoreGlobs") && !maybeIgnoreGlobs.IsUndefined()) {
if (!maybeIgnoreGlobs.IsArray()) {
throw Napi::TypeError::New(env, "options.ignoreGlobs must be a string array.");
}
Napi::Array array(maybeIgnoreGlobs.As<Napi::Array>());
uint32_t length(array.Length());
for (uint32_t i(0); i < length; ++i) {
Napi::Value item(array.Get(i));
if (!item.IsString()) {
throw Napi::TypeError::New(env, "options.ignoreGlobs must be a string array.");
}
mPathFilter->addIgnoreGlob(item.ToString().Utf8Value());
}
}
}
}

Expand Down Expand Up @@ -105,7 +124,7 @@ void NSFW::StartWorker::Execute() {
}

mNSFW->mQueue->clear();
mNSFW->mInterface.reset(new NativeInterface(mNSFW->mPath, mNSFW->mQueue));
mNSFW->mInterface.reset(new NativeInterface(mNSFW->mPath, mNSFW->mQueue, mNSFW->mPathFilter));

if (mNSFW->mInterface->isWatching()) {
mStatus = STARTED;
Expand Down Expand Up @@ -276,7 +295,7 @@ void NSFW::pollForEvents() {
}

if (mQueue->count() != 0) {
auto events = mQueue->dequeueAll();
auto events = mQueue->dequeueAll(mPathFilter);
if (events != nullptr) {
sleepDuration = mDebounceMS;
auto callback = [](Napi::Env env, Napi::Function jsCallback, std::vector<std::unique_ptr<Event>> *eventsRaw) {
Expand Down
4 changes: 2 additions & 2 deletions src/NativeInterface.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#include "../includes/NativeInterface.h"

NativeInterface::NativeInterface(const std::string &path, std::shared_ptr<EventQueue> queue) {
mNativeInterface.reset(new NativeImplementation(queue, path));
NativeInterface::NativeInterface(const std::string &path, std::shared_ptr<EventQueue> queue, std::shared_ptr<PathFilter> pathFilter) {
mNativeInterface.reset(new NativeImplementation(queue, path, pathFilter));
}

NativeInterface::~NativeInterface() {
Expand Down
Loading