Skip to content

Commit d2299ac

Browse files
author
Will
committed
watcher-nodejs: first draft of the watcher, for node js
1 parent 09bf85f commit d2299ac

File tree

10 files changed

+5459
-1
lines changed

10 files changed

+5459
-1
lines changed

libcwatcher/src/watcher-cabi.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ void* wtr_watcher_open(
3838
{
3939
auto wrapped_callback = [callback, context](wtr::watcher::event ev_owned)
4040
{
41-
wtr_watcher_event ev_view = {0};
41+
wtr_watcher_event ev_view = {};
4242
#ifdef _WIN32
4343
char path_name[PATH_BUF_LEN] = {0};
4444
char associated_path_name[PATH_BUF_LEN] = {0};

watcher-nodejs/.clang-format

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
AccessModifierOffset: -2
2+
AlignAfterOpenBracket: AlwaysBreak
3+
AlignArrayOfStructures: Right
4+
# AlignConsecutiveAssignments: None
5+
AlignConsecutiveBitFields:
6+
Enabled: true
7+
AcrossEmptyLines: true
8+
AcrossComments: true
9+
# AlignConsecutiveDeclarations: None
10+
# AlignConsecutiveMacros
11+
AlignEscapedNewlines: Left
12+
AlignOperands: AlignAfterOperator
13+
AlignTrailingComments: true
14+
# [v16+]
15+
# AlignTrailingComments:
16+
# Kind: Always
17+
# OverEmptyLines: 2
18+
AllowAllArgumentsOnNextLine: false
19+
AllowAllParametersOfDeclarationOnNextLine: false
20+
AllowShortBlocksOnASingleLine: Always # Empty
21+
AllowShortCaseLabelsOnASingleLine: true
22+
AllowShortEnumsOnASingleLine: true
23+
AllowShortFunctionsOnASingleLine: All # Empty
24+
AllowShortIfStatementsOnASingleLine: Never
25+
AllowShortLambdasOnASingleLine: All # Empty
26+
AllowShortLoopsOnASingleLine: true
27+
AlwaysBreakAfterReturnType: None
28+
AlwaysBreakBeforeMultilineStrings: false
29+
AlwaysBreakTemplateDeclarations: Yes
30+
BinPackArguments: false
31+
BinPackParameters: false
32+
BitFieldColonSpacing: Both
33+
# [v16+]
34+
# BreakAfterAttributes: Always
35+
BreakBeforeBinaryOperators: NonAssignment # All
36+
# break before function scope (incl. lambdas) and else statements
37+
BreakBeforeBraces: Custom
38+
BraceWrapping:
39+
AfterCaseLabel: false
40+
AfterClass: false
41+
# [v16+]
42+
AfterControlStatement: Never
43+
AfterEnum: false
44+
AfterFunction: true
45+
AfterNamespace: false
46+
AfterObjCDeclaration: false
47+
AfterStruct: false
48+
AfterUnion: false
49+
AfterExternBlock: false
50+
BeforeCatch: false
51+
BeforeElse: false
52+
BeforeLambdaBody: true
53+
BeforeWhile: false
54+
IndentBraces: false
55+
SplitEmptyFunction: false
56+
SplitEmptyRecord: false
57+
SplitEmptyNamespace: false
58+
BreakBeforeConceptDeclarations: Always
59+
BreakBeforeTernaryOperators: true
60+
BreakConstructorInitializers: BeforeComma
61+
BreakInheritanceList: BeforeColon
62+
BreakStringLiterals: true
63+
ColumnLimit: 120
64+
# CommentPragmas regex
65+
CompactNamespaces: false
66+
ContinuationIndentWidth: 2
67+
Cpp11BracedListStyle: true
68+
EmptyLineAfterAccessModifier: Never
69+
EmptyLineBeforeAccessModifier: Always
70+
FixNamespaceComments: false
71+
IncludeBlocks: Merge
72+
# IncludeCategories regex [maybe useful for tool/hone]
73+
IndentAccessModifiers: false
74+
IndentCaseBlocks: false
75+
IndentCaseLabels: true
76+
IndentExternBlock: false
77+
IndentGotoLabels: true
78+
# IndentPPDirectives: BeforeHash [messes up includes, nice w/modules]
79+
IndentRequiresClause: false
80+
IndentWidth: 2
81+
# [v16+]
82+
# InsertNewlineAtEOF: true
83+
# [v16+]
84+
# IntegerLiteralSeparator:
85+
# Binary: 4
86+
# Decimal: 3
87+
# Hex: 2
88+
# [v16+]
89+
# LineEnding: LF
90+
NamespaceIndentation: None
91+
PackConstructorInitializers: Never
92+
PenaltyBreakOpenParenthesis: 0
93+
PenaltyBreakBeforeFirstCallParameter: 0
94+
PointerAlignment: Left
95+
QualifierAlignment: Custom
96+
# [bad docs? 'friend' does not work]
97+
QualifierOrder: ['inline', 'static', 'constexpr', 'volatile', 'restrict', 'type', 'const']
98+
ReferenceAlignment: Left
99+
ReflowComments: true
100+
RequiresClausePosition: OwnLine
101+
# [v16+]
102+
# RequiresExpressionIndentation: OuterScope
103+
SeparateDefinitionBlocks: Always
104+
SortIncludes: CaseInsensitive
105+
# SortIncludes: true
106+
# [bad docs?]
107+
# SortUsingDeclarations: LexicographicNumeric
108+
SortUsingDeclarations: true
109+
SpaceAfterCStyleCast: false
110+
SpaceAfterLogicalNot: true # false
111+
SpaceAfterTemplateKeyword: false
112+
SpaceBeforeAssignmentOperators: true
113+
SpaceBeforeCaseColon: true
114+
SpaceBeforeCpp11BracedList: false # true
115+
SpaceBeforeRangeBasedForLoopColon: true
116+
SpaceBeforeSquareBrackets: false # true
117+
SpaceInEmptyBlock: false
118+
SpaceInEmptyParentheses: false
119+
SpacesBeforeTrailingComments: 2
120+
SpacesInConditionalStatement: false
121+
SpacesInContainerLiterals: true
122+
SpacesInAngles: Never
123+
SpacesInCStyleCastParentheses: false
124+
# SpacesInLineCommentPrefix
125+
SpacesInSquareBrackets: false
126+
Standard: Latest
127+
SpaceBeforeCtorInitializerColon: true
128+
SpaceBeforeInheritanceColon: true
129+
SpaceBeforeParens: ControlStatements # Never
130+
TabWidth: 2
131+
UseTab: Never

watcher-nodejs/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
build
2+
node_modules

watcher-nodejs/binding.gyp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"targets": [
3+
{
4+
"target_name": "watcher",
5+
"sources": [
6+
"../libcwatcher/src/watcher-cabi.cpp",
7+
"cwatcher_wrapper.cpp",
8+
],
9+
"include_dirs": [
10+
"../libcwatcher/include",
11+
],
12+
"link_settings": {
13+
"libraries": [
14+
"-Wl,-rpath,/usr/local/lib",
15+
]
16+
}
17+
}
18+
]
19+
}
20+

watcher-nodejs/cwatcher_wrapper.cpp

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#define NAPI_VERSION 8
2+
#include "wtr/watcher-cabi.h"
3+
#include <node_api.h>
4+
#include <stdio.h>
5+
#include <stdlib.h>
6+
#include <string.h>
7+
8+
#define WITH_TSFN_NONBLOCKING 1
9+
10+
/* Owns:
11+
- A "handle" to a watch object,
12+
which we can give back to the watcher
13+
when we want to close it.
14+
- A (thread-safe) function, storing the
15+
user's callback, which we can call when
16+
an event is ready.
17+
- A reference to the user's callback, which
18+
we release when the watcher is closed. */
19+
struct WatcherWrapper {
20+
napi_ref js_callback_ref = NULL;
21+
napi_threadsafe_function tsfn = NULL;
22+
void* watcher = NULL;
23+
};
24+
25+
/* Called by the callback bridge when an event is ready.
26+
Forwards event to js-land for the user.
27+
Expects an "owned" event, which will be freed in this scope. */
28+
static void callback_js_receiver(napi_env env, napi_value js_callback, void* _unused, void* ctx)
29+
{
30+
wtr_watcher_event* event = (wtr_watcher_event*)ctx;
31+
napi_value event_obj;
32+
napi_create_object(env, &event_obj);
33+
napi_value path_name, effect_type, path_type, effect_time, associated_path_name;
34+
napi_create_string_utf8(env, event->path_name, NAPI_AUTO_LENGTH, &path_name);
35+
napi_create_int32(env, event->effect_type, &effect_type);
36+
napi_create_int32(env, event->path_type, &path_type);
37+
napi_create_bigint_int64(env, event->effect_time, &effect_time);
38+
napi_set_named_property(env, event_obj, "pathName", path_name);
39+
napi_set_named_property(env, event_obj, "effectType", effect_type);
40+
napi_set_named_property(env, event_obj, "pathType", path_type);
41+
napi_set_named_property(env, event_obj, "effectTime", effect_time);
42+
if (event->associated_path_name) {
43+
napi_create_string_utf8(env, event->associated_path_name, NAPI_AUTO_LENGTH, &associated_path_name);
44+
napi_set_named_property(env, event_obj, "associatedPathName", associated_path_name);
45+
}
46+
napi_value global;
47+
napi_get_global(env, &global);
48+
napi_value result;
49+
napi_call_function(env, global, js_callback, 1, &event_obj, &result);
50+
#if WITH_TSFN_NONBLOCKING
51+
free((void*)event->path_name);
52+
free((void*)event->associated_path_name);
53+
free(event);
54+
#endif
55+
}
56+
57+
/* Called by the watcher when an event is ready.
58+
Passes a newly allocated event to the wrapper's stored function.
59+
Expects the event it to be freed in the wrapper's stored function. */
60+
static void callback_bridge(struct wtr_watcher_event event_view, void* ctx)
61+
{
62+
WatcherWrapper* wrapper = (WatcherWrapper*)ctx;
63+
#if WITH_TSFN_NONBLOCKING
64+
wtr_watcher_event* event_owned = (wtr_watcher_event*)malloc(sizeof(wtr_watcher_event));
65+
event_owned->path_name = event_view.path_name ? strdup(event_view.path_name) : NULL;
66+
event_owned->effect_type = event_view.effect_type;
67+
event_owned->path_type = event_view.path_type;
68+
event_owned->effect_time = event_view.effect_time;
69+
event_owned->associated_path_name = event_view.associated_path_name ? strdup(event_view.associated_path_name) : NULL;
70+
if (wrapper->tsfn) {
71+
napi_call_threadsafe_function(wrapper->tsfn, event_owned, napi_tsfn_nonblocking);
72+
#else
73+
napi_call_threadsafe_function(wrapper->tsfn, &event_view, napi_tsfn_blocking);
74+
#endif
75+
}
76+
}
77+
78+
/* Should be called by the user when they are done with the watcher.
79+
Ends event processing and releases any owned resources. */
80+
static napi_value close(napi_env env, napi_callback_info func_arg_info)
81+
{
82+
void* ctx;
83+
napi_get_cb_info(env, func_arg_info, NULL, NULL, NULL, &ctx);
84+
WatcherWrapper* wrapper = (WatcherWrapper*)ctx;
85+
if (wrapper->tsfn) {
86+
napi_release_threadsafe_function(wrapper->tsfn, napi_tsfn_release);
87+
wrapper->tsfn = NULL;
88+
}
89+
if (wrapper->js_callback_ref) {
90+
napi_delete_reference(env, wrapper->js_callback_ref);
91+
wrapper->js_callback_ref = NULL;
92+
}
93+
bool closed_ok = wtr_watcher_close(wrapper->watcher);
94+
free(wrapper);
95+
wrapper->watcher = NULL;
96+
napi_value result;
97+
napi_get_boolean(env, closed_ok, &result);
98+
return result;
99+
}
100+
101+
/* Opens a watcher on a path (and any children).
102+
Calls the provided callback when events happen.
103+
Accepts two arguments, a path and a callback.
104+
Returns an object with a single method: close.
105+
Call `.close()` when you don't want to watch
106+
things anymore. */
107+
static napi_value watch(napi_env env, napi_callback_info func_arg_info)
108+
{
109+
size_t argc = 2;
110+
napi_value args[2];
111+
napi_get_cb_info(env, func_arg_info, &argc, args, NULL, NULL);
112+
if (argc != 2) {
113+
napi_throw_error(env, NULL, "Wrong number of arguments");
114+
return NULL;
115+
}
116+
char path[4096];
117+
size_t path_len;
118+
napi_get_value_string_utf8(env, args[0], path, sizeof(path), &path_len);
119+
WatcherWrapper* wrapper = (WatcherWrapper*)malloc(sizeof(WatcherWrapper));
120+
napi_value js_callback = args[1];
121+
napi_create_reference(env, js_callback, 1, &wrapper->js_callback_ref);
122+
napi_value work_name;
123+
napi_create_string_utf8(env, "Watcher", NAPI_AUTO_LENGTH, &work_name);
124+
size_t work_queue_size = 1024;
125+
napi_create_threadsafe_function(
126+
env,
127+
js_callback,
128+
NULL,
129+
work_name,
130+
work_queue_size,
131+
1,
132+
wrapper,
133+
NULL,
134+
NULL,
135+
callback_js_receiver,
136+
&wrapper->tsfn);
137+
wrapper->watcher = wtr_watcher_open(path, callback_bridge, wrapper);
138+
if (wrapper->watcher == NULL) {
139+
napi_throw_error(env, NULL, "Failed to open watcher");
140+
return NULL;
141+
}
142+
napi_value watcher_obj;
143+
napi_create_object(env, &watcher_obj);
144+
napi_value close_func;
145+
napi_create_function(env, "close", NAPI_AUTO_LENGTH, close, wrapper, &close_func);
146+
napi_set_named_property(env, watcher_obj, "close", close_func);
147+
napi_wrap(env, watcher_obj, wrapper, NULL, NULL, NULL);
148+
return watcher_obj;
149+
}
150+
151+
/* Module initialization. (Does this work as a non-CommonJS module?) */
152+
static napi_value mod_init(napi_env env, napi_value exports)
153+
{
154+
napi_value watch_func;
155+
napi_create_function(env, NULL, 0, watch, NULL, &watch_func);
156+
napi_set_named_property(env, exports, "watch", watch_func);
157+
return exports;
158+
}
159+
160+
NAPI_MODULE(NODE_GYP_MODULE_NAME, mod_init)

watcher-nodejs/example-usage.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const watcher = require('./build/Debug/watcher');
2+
3+
const path = process.argv[2] || '.';
4+
var w = watcher.watch(path, (event) => {
5+
console.log(event);
6+
});
7+
8+
process.stdin.resume();
9+
process.stdin.on('data', () => {
10+
w.close();
11+
process.exit();
12+
});

0 commit comments

Comments
 (0)