-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
watcher-nodejs: first draft of the watcher, for node js
- Loading branch information
Will
committed
Jul 19, 2024
1 parent
09bf85f
commit d2299ac
Showing
10 changed files
with
5,459 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
AccessModifierOffset: -2 | ||
AlignAfterOpenBracket: AlwaysBreak | ||
AlignArrayOfStructures: Right | ||
# AlignConsecutiveAssignments: None | ||
AlignConsecutiveBitFields: | ||
Enabled: true | ||
AcrossEmptyLines: true | ||
AcrossComments: true | ||
# AlignConsecutiveDeclarations: None | ||
# AlignConsecutiveMacros | ||
AlignEscapedNewlines: Left | ||
AlignOperands: AlignAfterOperator | ||
AlignTrailingComments: true | ||
# [v16+] | ||
# AlignTrailingComments: | ||
# Kind: Always | ||
# OverEmptyLines: 2 | ||
AllowAllArgumentsOnNextLine: false | ||
AllowAllParametersOfDeclarationOnNextLine: false | ||
AllowShortBlocksOnASingleLine: Always # Empty | ||
AllowShortCaseLabelsOnASingleLine: true | ||
AllowShortEnumsOnASingleLine: true | ||
AllowShortFunctionsOnASingleLine: All # Empty | ||
AllowShortIfStatementsOnASingleLine: Never | ||
AllowShortLambdasOnASingleLine: All # Empty | ||
AllowShortLoopsOnASingleLine: true | ||
AlwaysBreakAfterReturnType: None | ||
AlwaysBreakBeforeMultilineStrings: false | ||
AlwaysBreakTemplateDeclarations: Yes | ||
BinPackArguments: false | ||
BinPackParameters: false | ||
BitFieldColonSpacing: Both | ||
# [v16+] | ||
# BreakAfterAttributes: Always | ||
BreakBeforeBinaryOperators: NonAssignment # All | ||
# break before function scope (incl. lambdas) and else statements | ||
BreakBeforeBraces: Custom | ||
BraceWrapping: | ||
AfterCaseLabel: false | ||
AfterClass: false | ||
# [v16+] | ||
AfterControlStatement: Never | ||
AfterEnum: false | ||
AfterFunction: true | ||
AfterNamespace: false | ||
AfterObjCDeclaration: false | ||
AfterStruct: false | ||
AfterUnion: false | ||
AfterExternBlock: false | ||
BeforeCatch: false | ||
BeforeElse: false | ||
BeforeLambdaBody: true | ||
BeforeWhile: false | ||
IndentBraces: false | ||
SplitEmptyFunction: false | ||
SplitEmptyRecord: false | ||
SplitEmptyNamespace: false | ||
BreakBeforeConceptDeclarations: Always | ||
BreakBeforeTernaryOperators: true | ||
BreakConstructorInitializers: BeforeComma | ||
BreakInheritanceList: BeforeColon | ||
BreakStringLiterals: true | ||
ColumnLimit: 120 | ||
# CommentPragmas regex | ||
CompactNamespaces: false | ||
ContinuationIndentWidth: 2 | ||
Cpp11BracedListStyle: true | ||
EmptyLineAfterAccessModifier: Never | ||
EmptyLineBeforeAccessModifier: Always | ||
FixNamespaceComments: false | ||
IncludeBlocks: Merge | ||
# IncludeCategories regex [maybe useful for tool/hone] | ||
IndentAccessModifiers: false | ||
IndentCaseBlocks: false | ||
IndentCaseLabels: true | ||
IndentExternBlock: false | ||
IndentGotoLabels: true | ||
# IndentPPDirectives: BeforeHash [messes up includes, nice w/modules] | ||
IndentRequiresClause: false | ||
IndentWidth: 2 | ||
# [v16+] | ||
# InsertNewlineAtEOF: true | ||
# [v16+] | ||
# IntegerLiteralSeparator: | ||
# Binary: 4 | ||
# Decimal: 3 | ||
# Hex: 2 | ||
# [v16+] | ||
# LineEnding: LF | ||
NamespaceIndentation: None | ||
PackConstructorInitializers: Never | ||
PenaltyBreakOpenParenthesis: 0 | ||
PenaltyBreakBeforeFirstCallParameter: 0 | ||
PointerAlignment: Left | ||
QualifierAlignment: Custom | ||
# [bad docs? 'friend' does not work] | ||
QualifierOrder: ['inline', 'static', 'constexpr', 'volatile', 'restrict', 'type', 'const'] | ||
ReferenceAlignment: Left | ||
ReflowComments: true | ||
RequiresClausePosition: OwnLine | ||
# [v16+] | ||
# RequiresExpressionIndentation: OuterScope | ||
SeparateDefinitionBlocks: Always | ||
SortIncludes: CaseInsensitive | ||
# SortIncludes: true | ||
# [bad docs?] | ||
# SortUsingDeclarations: LexicographicNumeric | ||
SortUsingDeclarations: true | ||
SpaceAfterCStyleCast: false | ||
SpaceAfterLogicalNot: true # false | ||
SpaceAfterTemplateKeyword: false | ||
SpaceBeforeAssignmentOperators: true | ||
SpaceBeforeCaseColon: true | ||
SpaceBeforeCpp11BracedList: false # true | ||
SpaceBeforeRangeBasedForLoopColon: true | ||
SpaceBeforeSquareBrackets: false # true | ||
SpaceInEmptyBlock: false | ||
SpaceInEmptyParentheses: false | ||
SpacesBeforeTrailingComments: 2 | ||
SpacesInConditionalStatement: false | ||
SpacesInContainerLiterals: true | ||
SpacesInAngles: Never | ||
SpacesInCStyleCastParentheses: false | ||
# SpacesInLineCommentPrefix | ||
SpacesInSquareBrackets: false | ||
Standard: Latest | ||
SpaceBeforeCtorInitializerColon: true | ||
SpaceBeforeInheritanceColon: true | ||
SpaceBeforeParens: ControlStatements # Never | ||
TabWidth: 2 | ||
UseTab: Never |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
build | ||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
"targets": [ | ||
{ | ||
"target_name": "watcher", | ||
"sources": [ | ||
"../libcwatcher/src/watcher-cabi.cpp", | ||
"cwatcher_wrapper.cpp", | ||
], | ||
"include_dirs": [ | ||
"../libcwatcher/include", | ||
], | ||
"link_settings": { | ||
"libraries": [ | ||
"-Wl,-rpath,/usr/local/lib", | ||
] | ||
} | ||
} | ||
] | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
#define NAPI_VERSION 8 | ||
#include "wtr/watcher-cabi.h" | ||
#include <node_api.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
|
||
#define WITH_TSFN_NONBLOCKING 1 | ||
|
||
/* Owns: | ||
- A "handle" to a watch object, | ||
which we can give back to the watcher | ||
when we want to close it. | ||
- A (thread-safe) function, storing the | ||
user's callback, which we can call when | ||
an event is ready. | ||
- A reference to the user's callback, which | ||
we release when the watcher is closed. */ | ||
struct WatcherWrapper { | ||
napi_ref js_callback_ref = NULL; | ||
napi_threadsafe_function tsfn = NULL; | ||
void* watcher = NULL; | ||
}; | ||
|
||
/* Called by the callback bridge when an event is ready. | ||
Forwards event to js-land for the user. | ||
Expects an "owned" event, which will be freed in this scope. */ | ||
static void callback_js_receiver(napi_env env, napi_value js_callback, void* _unused, void* ctx) | ||
{ | ||
wtr_watcher_event* event = (wtr_watcher_event*)ctx; | ||
napi_value event_obj; | ||
napi_create_object(env, &event_obj); | ||
napi_value path_name, effect_type, path_type, effect_time, associated_path_name; | ||
napi_create_string_utf8(env, event->path_name, NAPI_AUTO_LENGTH, &path_name); | ||
napi_create_int32(env, event->effect_type, &effect_type); | ||
napi_create_int32(env, event->path_type, &path_type); | ||
napi_create_bigint_int64(env, event->effect_time, &effect_time); | ||
napi_set_named_property(env, event_obj, "pathName", path_name); | ||
napi_set_named_property(env, event_obj, "effectType", effect_type); | ||
napi_set_named_property(env, event_obj, "pathType", path_type); | ||
napi_set_named_property(env, event_obj, "effectTime", effect_time); | ||
if (event->associated_path_name) { | ||
napi_create_string_utf8(env, event->associated_path_name, NAPI_AUTO_LENGTH, &associated_path_name); | ||
napi_set_named_property(env, event_obj, "associatedPathName", associated_path_name); | ||
} | ||
napi_value global; | ||
napi_get_global(env, &global); | ||
napi_value result; | ||
napi_call_function(env, global, js_callback, 1, &event_obj, &result); | ||
#if WITH_TSFN_NONBLOCKING | ||
free((void*)event->path_name); | ||
free((void*)event->associated_path_name); | ||
free(event); | ||
#endif | ||
} | ||
|
||
/* Called by the watcher when an event is ready. | ||
Passes a newly allocated event to the wrapper's stored function. | ||
Expects the event it to be freed in the wrapper's stored function. */ | ||
static void callback_bridge(struct wtr_watcher_event event_view, void* ctx) | ||
{ | ||
WatcherWrapper* wrapper = (WatcherWrapper*)ctx; | ||
#if WITH_TSFN_NONBLOCKING | ||
wtr_watcher_event* event_owned = (wtr_watcher_event*)malloc(sizeof(wtr_watcher_event)); | ||
event_owned->path_name = event_view.path_name ? strdup(event_view.path_name) : NULL; | ||
event_owned->effect_type = event_view.effect_type; | ||
event_owned->path_type = event_view.path_type; | ||
event_owned->effect_time = event_view.effect_time; | ||
event_owned->associated_path_name = event_view.associated_path_name ? strdup(event_view.associated_path_name) : NULL; | ||
if (wrapper->tsfn) { | ||
napi_call_threadsafe_function(wrapper->tsfn, event_owned, napi_tsfn_nonblocking); | ||
#else | ||
napi_call_threadsafe_function(wrapper->tsfn, &event_view, napi_tsfn_blocking); | ||
#endif | ||
} | ||
} | ||
|
||
/* Should be called by the user when they are done with the watcher. | ||
Ends event processing and releases any owned resources. */ | ||
static napi_value close(napi_env env, napi_callback_info func_arg_info) | ||
{ | ||
void* ctx; | ||
napi_get_cb_info(env, func_arg_info, NULL, NULL, NULL, &ctx); | ||
WatcherWrapper* wrapper = (WatcherWrapper*)ctx; | ||
if (wrapper->tsfn) { | ||
napi_release_threadsafe_function(wrapper->tsfn, napi_tsfn_release); | ||
wrapper->tsfn = NULL; | ||
} | ||
if (wrapper->js_callback_ref) { | ||
napi_delete_reference(env, wrapper->js_callback_ref); | ||
wrapper->js_callback_ref = NULL; | ||
} | ||
bool closed_ok = wtr_watcher_close(wrapper->watcher); | ||
free(wrapper); | ||
wrapper->watcher = NULL; | ||
napi_value result; | ||
napi_get_boolean(env, closed_ok, &result); | ||
return result; | ||
} | ||
|
||
/* Opens a watcher on a path (and any children). | ||
Calls the provided callback when events happen. | ||
Accepts two arguments, a path and a callback. | ||
Returns an object with a single method: close. | ||
Call `.close()` when you don't want to watch | ||
things anymore. */ | ||
static napi_value watch(napi_env env, napi_callback_info func_arg_info) | ||
{ | ||
size_t argc = 2; | ||
napi_value args[2]; | ||
napi_get_cb_info(env, func_arg_info, &argc, args, NULL, NULL); | ||
if (argc != 2) { | ||
napi_throw_error(env, NULL, "Wrong number of arguments"); | ||
return NULL; | ||
} | ||
char path[4096]; | ||
size_t path_len; | ||
napi_get_value_string_utf8(env, args[0], path, sizeof(path), &path_len); | ||
WatcherWrapper* wrapper = (WatcherWrapper*)malloc(sizeof(WatcherWrapper)); | ||
napi_value js_callback = args[1]; | ||
napi_create_reference(env, js_callback, 1, &wrapper->js_callback_ref); | ||
napi_value work_name; | ||
napi_create_string_utf8(env, "Watcher", NAPI_AUTO_LENGTH, &work_name); | ||
size_t work_queue_size = 1024; | ||
napi_create_threadsafe_function( | ||
env, | ||
js_callback, | ||
NULL, | ||
work_name, | ||
work_queue_size, | ||
1, | ||
wrapper, | ||
NULL, | ||
NULL, | ||
callback_js_receiver, | ||
&wrapper->tsfn); | ||
wrapper->watcher = wtr_watcher_open(path, callback_bridge, wrapper); | ||
if (wrapper->watcher == NULL) { | ||
napi_throw_error(env, NULL, "Failed to open watcher"); | ||
return NULL; | ||
} | ||
napi_value watcher_obj; | ||
napi_create_object(env, &watcher_obj); | ||
napi_value close_func; | ||
napi_create_function(env, "close", NAPI_AUTO_LENGTH, close, wrapper, &close_func); | ||
napi_set_named_property(env, watcher_obj, "close", close_func); | ||
napi_wrap(env, watcher_obj, wrapper, NULL, NULL, NULL); | ||
return watcher_obj; | ||
} | ||
|
||
/* Module initialization. (Does this work as a non-CommonJS module?) */ | ||
static napi_value mod_init(napi_env env, napi_value exports) | ||
{ | ||
napi_value watch_func; | ||
napi_create_function(env, NULL, 0, watch, NULL, &watch_func); | ||
napi_set_named_property(env, exports, "watch", watch_func); | ||
return exports; | ||
} | ||
|
||
NAPI_MODULE(NODE_GYP_MODULE_NAME, mod_init) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
const watcher = require('./build/Debug/watcher'); | ||
|
||
const path = process.argv[2] || '.'; | ||
var w = watcher.watch(path, (event) => { | ||
console.log(event); | ||
}); | ||
|
||
process.stdin.resume(); | ||
process.stdin.on('data', () => { | ||
w.close(); | ||
process.exit(); | ||
}); |
Oops, something went wrong.