wdm 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,28 +1,28 @@
1
- #ifndef WDM_RB_CHANGE_H
2
- #define WDM_RB_CHANGE_H
3
-
4
- #ifdef __cplusplus
5
- extern "C" {
6
- #endif // __cplusplus
7
-
8
- // ----------------------------------------------------------
9
- // Global variables
10
- // ----------------------------------------------------------
11
-
12
- extern VALUE cWDM_Change;
13
-
14
- // ---------------------------------------------------------
15
- // Prototypes
16
- // ---------------------------------------------------------
17
-
18
- VALUE wdm_rb_change_new_from_notification(const LPWSTR, const PFILE_NOTIFY_INFORMATION);
19
-
20
- void wdm_rb_change_init();
21
-
22
- // ---------------------------------------------------------
23
-
24
- #ifdef __cplusplus
25
- }
26
- #endif // __cplusplus
27
-
1
+ #ifndef WDM_RB_CHANGE_H
2
+ #define WDM_RB_CHANGE_H
3
+
4
+ #ifdef __cplusplus
5
+ extern "C" {
6
+ #endif // __cplusplus
7
+
8
+ // ----------------------------------------------------------
9
+ // Global variables
10
+ // ----------------------------------------------------------
11
+
12
+ extern VALUE cWDM_Change;
13
+
14
+ // ---------------------------------------------------------
15
+ // Prototypes
16
+ // ---------------------------------------------------------
17
+
18
+ VALUE wdm_rb_change_new_from_notification(const LPWSTR, const PFILE_NOTIFY_INFORMATION);
19
+
20
+ void wdm_rb_change_init();
21
+
22
+ // ---------------------------------------------------------
23
+
24
+ #ifdef __cplusplus
25
+ }
26
+ #endif // __cplusplus
27
+
28
28
  #endif // WDM_RB_CHANGE_H
@@ -1,571 +1,575 @@
1
- #include "wdm.h"
2
-
3
- #include "utils.h"
4
- #include "entry.h"
5
- #include "queue.h"
6
- #include "monitor.h"
7
-
8
- #include "rb_change.h"
9
- #include "rb_monitor.h"
10
-
11
- // ----------------------------------------------------------
12
- // Global variables
13
- // ----------------------------------------------------------
14
-
15
- VALUE cWDM_Monitor;
16
-
17
- VALUE eWDM_UnknownFlagError;
18
-
19
- // ----------------------------------------------------------
20
- // Cached variables
21
- // ----------------------------------------------------------
22
-
23
- static ID wdm_rb_sym_call;
24
- static ID wdm_rb_sym_files;
25
- static ID wdm_rb_sym_directories;
26
- static ID wdm_rb_sym_attributes;
27
- static ID wdm_rb_sym_size;
28
- static ID wdm_rb_sym_last_write;
29
- static ID wdm_rb_sym_last_access;
30
- static ID wdm_rb_sym_creation;
31
- static ID wdm_rb_sym_security;
32
- static ID wdm_rb_sym_default;
33
-
34
- // ---------------------------------------------------------
35
- // Prototypes of static functions
36
- // ---------------------------------------------------------
37
-
38
- static void monitor_mark(LPVOID);
39
- static void monitor_free(LPVOID);
40
- static VALUE rb_monitor_alloc(VALUE);
41
-
42
- static DWORD id_to_flag(ID);
43
- static DWORD extract_flags_from_rb_array(VALUE);
44
- static VALUE combined_watch(BOOL, int, VALUE*, VALUE);
45
- static VALUE rb_monitor_watch(int, VALUE*, VALUE);
46
-
47
- static VALUE rb_monitor_watch_recursively(int, VALUE*, VALUE);
48
-
49
- static void CALLBACK handle_entry_change(DWORD, DWORD, LPOVERLAPPED);
50
- static BOOL register_monitoring_entry(WDM_PEntry);
51
- static DWORD WINAPI start_monitoring(LPVOID);
52
-
53
- static VALUE wait_for_changes(LPVOID);
54
- static void process_changes(WDM_PQueue);
55
- static void stop_monitoring(LPVOID);
56
- static VALUE rb_monitor_run_bang(VALUE);
57
-
58
- static VALUE rb_monitor_stop(VALUE);
59
-
60
- // ----------------------------------------------------------
61
-
62
- static void
63
- monitor_mark(LPVOID param)
64
- {
65
- WDM_PMonitor monitor;
66
- WDM_PEntry entry;
67
-
68
- monitor = (WDM_PMonitor)param;
69
- entry = monitor->head;
70
-
71
- while(entry != NULL) {
72
- rb_gc_mark(entry->user_data->callback);
73
- entry = entry->next;
74
- }
75
- }
76
-
77
- static void
78
- monitor_free(LPVOID param)
79
- {
80
- WDM_PMonitor monitor;
81
- WDM_PEntry entry;
82
-
83
- WDM_DEBUG("Freeing a monitor object!");
84
-
85
- monitor = (WDM_PMonitor)param;
86
- entry = monitor->head;
87
-
88
- stop_monitoring(monitor); // If the monitor is already stopped, it would do nothing
89
-
90
- while(entry != NULL) {
91
- if ( entry->event_container.hEvent != NULL ) {
92
- wdm_monitor_callback_param_free(
93
- (WDM_PMonitorCallbackParam)entry->event_container.hEvent
94
- );
95
- }
96
- entry = entry->next;
97
- }
98
-
99
- wdm_monitor_free(monitor);
100
- }
101
-
102
- static VALUE
103
- rb_monitor_alloc(VALUE self)
104
- {
105
- WDM_DEBUG("--------------------------------");
106
- WDM_DEBUG("Allocating a new monitor object!");
107
- WDM_DEBUG("--------------------------------");
108
-
109
- return Data_Wrap_Struct(self, monitor_mark, monitor_free, wdm_monitor_new());
110
- }
111
-
112
- static DWORD
113
- id_to_flag(ID id)
114
- {
115
- if ( id == wdm_rb_sym_default ) return WDM_MONITOR_FLAGS_DEFAULT;
116
-
117
- // TODO: Maybe reorder the if's in the frequency of use for better performance?
118
- if ( id == wdm_rb_sym_files ) return FILE_NOTIFY_CHANGE_FILE_NAME;
119
- if ( id == wdm_rb_sym_directories ) return FILE_NOTIFY_CHANGE_DIR_NAME;
120
- if ( id == wdm_rb_sym_attributes ) return FILE_NOTIFY_CHANGE_ATTRIBUTES;
121
- if ( id == wdm_rb_sym_size ) return FILE_NOTIFY_CHANGE_SIZE;
122
- if ( id == wdm_rb_sym_last_write ) return FILE_NOTIFY_CHANGE_LAST_WRITE;
123
- if ( id == wdm_rb_sym_last_access ) return FILE_NOTIFY_CHANGE_LAST_ACCESS;
124
- if ( id == wdm_rb_sym_creation ) return FILE_NOTIFY_CHANGE_CREATION;
125
- if ( id == wdm_rb_sym_security ) return FILE_NOTIFY_CHANGE_SECURITY;
126
-
127
- rb_raise(eWDM_UnknownFlagError, "Unknown watch flag: ':%s'", rb_id2name(id));
128
- }
129
-
130
- static DWORD
131
- extract_flags_from_rb_array(VALUE flags_array)
132
- {
133
- VALUE flag_symbol;
134
- DWORD flags;
135
-
136
- flags = 0;
137
-
138
- while ( RARRAY_LEN(flags_array) != 0 ) {
139
- flag_symbol = rb_ary_pop(flags_array);
140
- Check_Type(flag_symbol, T_SYMBOL);
141
- flags |= id_to_flag( SYM2ID(flag_symbol) );
142
- }
143
-
144
- return flags;
145
- }
146
-
147
- static VALUE
148
- combined_watch(BOOL recursively, int argc, VALUE *argv, VALUE self)
149
- {
150
- WDM_PMonitor monitor;
151
- WDM_PEntry entry;
152
- int directory_letters_count;
153
- VALUE directory, flags, os_encoded_directory;
154
- BOOL running;
155
-
156
- // TODO: Maybe raise a more user-friendly error?
157
- rb_need_block();
158
-
159
- Data_Get_Struct(self, WDM_Monitor, monitor);
160
-
161
- EnterCriticalSection(&monitor->lock);
162
- running = monitor->running;
163
- LeaveCriticalSection(&monitor->lock);
164
-
165
- if ( running ) {
166
- rb_raise(eWDM_MonitorRunningError, "You can't watch new directories while the monitor is running!");
167
- }
168
-
169
- rb_scan_args(argc, argv, "1*", &directory, &flags);
170
-
171
- Check_Type(directory, T_STRING);
172
-
173
- entry = wdm_entry_new();
174
- entry->user_data->watch_childeren = recursively;
175
- entry->user_data->callback = rb_block_proc();
176
- entry->user_data->flags = RARRAY_LEN(flags) == 0 ? WDM_MONITOR_FLAGS_DEFAULT : extract_flags_from_rb_array(flags);
177
-
178
- // WTF Ruby source: The original code (file.c) uses the following macro to make sure that the encoding
179
- // of the string is ASCII-compatible, but UTF-16LE (Windows default encoding) is not!!!
180
- //
181
- // FilePathValue(directory);
182
-
183
- os_encoded_directory = rb_str_encode_ospath(directory);
184
-
185
- // RSTRING_LEN can't be used because it would return the count of bytes the string uses in its encoding (like UTF-8).
186
- // UTF-8 might use more than one byte for the char, which is not needed for WCHAR strings.
187
- // Also, the result of MultiByteToWideChar _includes_ the NULL char at the end, which is not true for RSTRING.
188
- //
189
- // Example: 'C:\Users\Maher\Desktop\تجربة' with __ENCODING__ == UTF-8
190
- // MultiByteToWideChar => 29 (28-char + null)
191
- // RSTRING_LEN => 33 (23-char + 10-bytes for 5 Arabic letters which take 2 bytes each)
192
- //
193
- directory_letters_count = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(os_encoded_directory), -1, NULL, 0);
194
-
195
- entry->user_data->dir = ALLOCA_N(WCHAR, directory_letters_count);
196
-
197
- MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(os_encoded_directory), -1, entry->user_data->dir, directory_letters_count);
198
-
199
- WDM_WDEBUG("New path to watch: '%s'", entry->user_data->dir);
200
-
201
- entry->user_data->dir = wdm_utils_full_pathname(entry->user_data->dir);
202
-
203
- if ( entry->user_data->dir == 0 ) {
204
- wdm_entry_free(entry);
205
- rb_raise(eWDM_Error, "Can't get the absolute path for the passed directory: '%s'!", RSTRING_PTR(directory));
206
- }
207
-
208
- if ( ! wdm_utils_unicode_is_directory(entry->user_data->dir) ) {
209
- wdm_entry_free(entry);
210
- rb_raise(eWDM_InvalidDirectoryError, "No such directory: '%s'!", RSTRING_PTR(directory));
211
- }
212
-
213
- // Tell the GC to collect the tmp string
214
- rb_str_resize(os_encoded_directory, 0);
215
-
216
- entry->dir_handle = CreateFileW(
217
- entry->user_data->dir, // pointer to the file name
218
- FILE_LIST_DIRECTORY, // access (read/write) mode
219
- FILE_SHARE_READ // share mode
220
- | FILE_SHARE_WRITE
221
- | FILE_SHARE_DELETE,
222
- NULL, // security descriptor
223
- OPEN_EXISTING, // how to create
224
- FILE_FLAG_BACKUP_SEMANTICS
225
- | FILE_FLAG_OVERLAPPED, // file attributes
226
- NULL
227
- );
228
-
229
- if ( entry->dir_handle == INVALID_HANDLE_VALUE ) {
230
- wdm_entry_free(entry);
231
- rb_raise(eWDM_Error, "Can't watch directory: '%s'!", RSTRING_PTR(directory));
232
- }
233
-
234
- // Store a reference to the entry instead of an event as the event
235
- // won't be used when using callbacks.
236
- entry->event_container.hEvent = wdm_monitor_callback_param_new(monitor, entry);
237
-
238
- wdm_monitor_update_head(monitor, entry);
239
-
240
- WDM_WDEBUG("Watching directory: '%s'", entry->user_data->dir);
241
-
242
- return Qnil;
243
- }
244
-
245
- static VALUE
246
- rb_monitor_watch(int argc, VALUE *argv, VALUE self)
247
- {
248
- return combined_watch(FALSE, argc, argv, self);
249
- }
250
-
251
- static VALUE
252
- rb_monitor_watch_recursively(int argc, VALUE *argv, VALUE self)
253
- {
254
- return combined_watch(TRUE, argc, argv, self);
255
- }
256
-
257
- static void CALLBACK
258
- handle_entry_change(
259
- DWORD err_code, // completion code
260
- DWORD bytes_transfered, // number of bytes transferred
261
- LPOVERLAPPED event_container // I/O information buffer
262
- ) {
263
- WDM_PMonitorCallbackParam param;
264
- WDM_PQueueItem data_to_process;
265
-
266
- if ( err_code == ERROR_OPERATION_ABORTED ) {
267
- // Async operation was canceled. This shouldn't happen.
268
- // TODO:
269
- // 1. Maybe add a union in the queue for errors?
270
- // 2. What's the best action when this happens?
271
- WDM_DEBUG("Dir handler closed in the process callback!");
272
- return;
273
- }
274
-
275
- if ( ! bytes_transfered ) {
276
- WDM_DEBUG("Buffer overflow?! Changes are bigger than the buffer!");
277
- return;
278
- }
279
-
280
- param = (WDM_PMonitorCallbackParam)event_container->hEvent;
281
- data_to_process = wdm_queue_item_new(WDM_QUEUE_ITEM_TYPE_DATA);
282
- data_to_process->data = wdm_queue_item_data_new();
283
-
284
- WDM_WDEBUG("Change detected in '%s'", param->entry->user_data->dir);
285
-
286
- data_to_process->data->user_data = param->entry->user_data;
287
-
288
- // Copy change data to the backup buffer
289
- memcpy(data_to_process->data->buffer, param->entry->buffer, bytes_transfered);
290
-
291
- // Add the backup buffer to the change queue
292
- wdm_queue_enqueue(param->monitor->changes, data_to_process);
293
-
294
- // Resume watching the dir for changes
295
- register_monitoring_entry(param->entry);
296
-
297
- // Tell the processing thread to process the changes
298
- if ( WaitForSingleObject(param->monitor->process_event, 0) != WAIT_OBJECT_0 ) { // Check if already signaled
299
- SetEvent(param->monitor->process_event);
300
- }
301
- }
302
-
303
- static BOOL
304
- register_monitoring_entry(WDM_PEntry entry)
305
- {
306
- BOOL success;
307
- DWORD bytes;
308
- bytes = 0; // Not used because the process callback gets passed the written bytes
309
-
310
- success = ReadDirectoryChangesW(
311
- entry->dir_handle, // handle to directory
312
- entry->buffer, // read results buffer
313
- WDM_BUFFER_SIZE, // length of buffer
314
- entry->user_data->watch_childeren, // monitoring option
315
- entry->user_data->flags, // filter conditions
316
- &bytes, // bytes returned
317
- &entry->event_container, // overlapped buffer
318
- &handle_entry_change // process callback
319
- );
320
-
321
- if ( ! success ) {
322
- WDM_DEBUG("ReadDirectoryChangesW failed with error (%d): %s", GetLastError(), rb_w32_strerror(GetLastError()));
323
- return FALSE;
324
- }
325
-
326
- return TRUE;
327
- }
328
-
329
- static DWORD WINAPI
330
- start_monitoring(LPVOID param)
331
- {
332
- WDM_PMonitor monitor;
333
- WDM_PEntry curr_entry;
334
-
335
- monitor = (WDM_PMonitor)param;
336
- curr_entry = monitor->head;
337
-
338
- WDM_DEBUG("Starting the monitoring thread!");
339
-
340
- while(curr_entry != NULL) {
341
- if ( ! register_monitoring_entry(curr_entry) ) {
342
- WDM_PQueueItem error_item;
343
- int directory_bytes;
344
- LPSTR multibyte_directory;
345
-
346
- directory_bytes = WideCharToMultiByte(CP_UTF8, 0, curr_entry->user_data->dir, -1, NULL, 0, NULL, NULL);
347
- multibyte_directory = ALLOCA_N(CHAR, directory_bytes);
348
- WideCharToMultiByte(CP_UTF8, 0, curr_entry->user_data->dir, -1, multibyte_directory, directory_bytes, NULL, NULL);
349
-
350
- error_item = wdm_queue_item_new(WDM_QUEUE_ITEM_TYPE_ERROR);
351
- error_item->error = wdm_queue_item_error_new(
352
- eWDM_UnwatchableDirectoryError, "Can't watch directory: '%s'!", multibyte_directory
353
- );
354
-
355
- wdm_queue_enqueue(monitor->changes, error_item);
356
- SetEvent(monitor->process_event);
357
- }
358
-
359
- curr_entry = curr_entry->next;
360
- }
361
-
362
- while(monitor->running) {
363
- // TODO: Is this the best way to do it?
364
- if ( WaitForSingleObjectEx(monitor->stop_event, INFINITE, TRUE) == WAIT_OBJECT_0) {
365
- WDM_DEBUG("Exiting the monitoring thread!");
366
- ExitThread(0);
367
- }
368
- }
369
-
370
- return 0;
371
- }
372
-
373
- static VALUE
374
- wait_for_changes(LPVOID param)
375
- {
376
- HANDLE process_event;
377
-
378
- process_event = (HANDLE)param;
379
-
380
- return WaitForSingleObject(process_event, INFINITE) == WAIT_OBJECT_0 ? Qtrue : Qfalse;
381
- }
382
-
383
- static void
384
- process_changes(WDM_PQueue changes)
385
- {
386
- WDM_PQueueItem item;
387
- LPBYTE current_info_entry_offset;
388
- PFILE_NOTIFY_INFORMATION info;
389
- VALUE event;
390
-
391
- WDM_DEBUG("---------------------------");
392
- WDM_DEBUG("Process changes");
393
- WDM_DEBUG("--------------------------");
394
-
395
- while( ! wdm_queue_is_empty(changes) ) {
396
- item = wdm_queue_dequeue(changes);
397
-
398
- if ( item->type == WDM_QUEUE_ITEM_TYPE_ERROR ) {
399
- rb_raise(item->error->exception_klass, item->error->message);
400
- }
401
- else {
402
- current_info_entry_offset = (LPBYTE)item->data->buffer;
403
-
404
- for(;;) {
405
- info = (PFILE_NOTIFY_INFORMATION)current_info_entry_offset;
406
- event = wdm_rb_change_new_from_notification(item->data->user_data->dir, info);
407
-
408
- WDM_DEBUG("---------------------------");
409
- WDM_DEBUG("Running user callback");
410
- WDM_DEBUG("--------------------------");
411
-
412
- rb_funcall(item->data->user_data->callback, wdm_rb_sym_call, 1, event);
413
-
414
- WDM_DEBUG("---------------------------");
415
-
416
- if ( ! info->NextEntryOffset ) break;
417
-
418
- current_info_entry_offset += info->NextEntryOffset;
419
- }
420
- }
421
-
422
- wdm_queue_item_free(item);
423
- }
424
- }
425
-
426
- static void
427
- stop_monitoring(LPVOID param)
428
- {
429
- BOOL already_stopped;
430
- WDM_PMonitor monitor;
431
- WDM_PEntry entry;
432
-
433
- monitor = (WDM_PMonitor)param;
434
- already_stopped = FALSE;
435
-
436
- WDM_DEBUG("Stopping the monitor!");
437
-
438
- EnterCriticalSection(&monitor->lock);
439
- if ( ! monitor->running ) {
440
- already_stopped = TRUE;
441
- }
442
- else {
443
- monitor->running = FALSE;
444
- }
445
- LeaveCriticalSection(&monitor->lock);
446
-
447
- if (already_stopped) {
448
- WDM_DEBUG("Can't stop monitoring because it's already stopped (or it's never been started)!!");
449
- return;
450
- }
451
-
452
- entry = monitor->head;
453
-
454
- while(entry != NULL) {
455
- CancelIo(entry->dir_handle); // Stop monitoring changes
456
- entry = entry->next;
457
- }
458
-
459
- SetEvent(monitor->stop_event);
460
- SetEvent(monitor->process_event); // The process code checks after the wait for an exit signal
461
- WaitForSingleObject(monitor->monitoring_thread, 10000);
462
- }
463
-
464
- static VALUE
465
- rb_monitor_run_bang(VALUE self)
466
- {
467
- BOOL already_running,
468
- waiting_succeeded;
469
- WDM_PMonitor monitor;
470
-
471
- WDM_DEBUG("Running the monitor!");
472
-
473
- Data_Get_Struct(self, WDM_Monitor, monitor);
474
- already_running = FALSE;
475
-
476
- EnterCriticalSection(&monitor->lock);
477
- if ( monitor->running ) {
478
- already_running = TRUE;
479
- }
480
- else {
481
- monitor->running = TRUE;
482
- }
483
- LeaveCriticalSection(&monitor->lock);
484
-
485
- if (already_running) {
486
- WDM_DEBUG("Not doing anything because the monitor is already running!");
487
- return Qnil;
488
- }
489
-
490
- // Reset events
491
- ResetEvent(monitor->process_event);
492
- ResetEvent(monitor->stop_event);
493
-
494
- monitor->monitoring_thread = CreateThread(
495
- NULL, // default security attributes
496
- 0, // use default stack size
497
- start_monitoring, // thread function name
498
- monitor, // argument to thread function
499
- 0, // use default creation flags
500
- NULL // Ignore thread identifier
501
- );
502
-
503
- if ( monitor->monitoring_thread == NULL ) {
504
- rb_raise(eWDM_Error, "Can't create a thread for the monitor!");
505
- }
506
-
507
- while ( monitor->running ) {
508
- waiting_succeeded = rb_thread_blocking_region(wait_for_changes, monitor->process_event, stop_monitoring, monitor);
509
-
510
- if ( waiting_succeeded == Qfalse ) {
511
- rb_raise(eWDM_Error, "Failed while waiting for a change in the watched directories!");
512
- }
513
-
514
- if ( ! monitor->running ) {
515
- wdm_queue_empty(monitor->changes);
516
- return Qnil;
517
- }
518
-
519
- process_changes(monitor->changes);
520
-
521
- if ( ! ResetEvent(monitor->process_event) ) {
522
- rb_raise(eWDM_Error, "Couldn't reset system events to watch for changes!");
523
- }
524
- }
525
-
526
- return Qnil;
527
- }
528
-
529
- static VALUE
530
- rb_monitor_stop(VALUE self)
531
- {
532
- WDM_PMonitor monitor;
533
-
534
- Data_Get_Struct(self, WDM_Monitor, monitor);
535
-
536
- stop_monitoring(monitor);
537
-
538
- WDM_DEBUG("Stopped the monitor!");
539
-
540
- return Qnil;
541
- }
542
-
543
- void
544
- wdm_rb_monitor_init()
545
- {
546
- WDM_DEBUG("Registering WDM::Monitor with Ruby!");
547
-
548
- wdm_rb_sym_call = rb_intern("call");
549
- wdm_rb_sym_files = rb_intern("files");
550
- wdm_rb_sym_directories = rb_intern("directories");
551
- wdm_rb_sym_attributes = rb_intern("attributes");
552
- wdm_rb_sym_size = rb_intern("size");
553
- wdm_rb_sym_last_write = rb_intern("last_write");
554
- wdm_rb_sym_last_access = rb_intern("last_access");
555
- wdm_rb_sym_creation = rb_intern("creation");
556
- wdm_rb_sym_security = rb_intern("security");
557
- wdm_rb_sym_default = rb_intern("default");
558
-
559
- eWDM_MonitorRunningError = rb_define_class_under(mWDM, "MonitorRunningError", eWDM_Error);
560
- eWDM_InvalidDirectoryError = rb_define_class_under(mWDM, "InvalidDirectoryError", eWDM_Error);
561
- eWDM_UnknownFlagError = rb_define_class_under(mWDM, "UnknownFlagError", eWDM_Error);
562
- eWDM_UnwatchableDirectoryError = rb_define_class_under(mWDM, "UnwatchableDirectoryError", eWDM_Error);
563
-
564
- cWDM_Monitor = rb_define_class_under(mWDM, "Monitor", rb_cObject);
565
-
566
- rb_define_alloc_func(cWDM_Monitor, rb_monitor_alloc);
567
- rb_define_method(cWDM_Monitor, "watch", RUBY_METHOD_FUNC(rb_monitor_watch), -1);
568
- rb_define_method(cWDM_Monitor, "watch_recursively", RUBY_METHOD_FUNC(rb_monitor_watch_recursively), -1);
569
- rb_define_method(cWDM_Monitor, "run!", RUBY_METHOD_FUNC(rb_monitor_run_bang), 0);
570
- rb_define_method(cWDM_Monitor, "stop", RUBY_METHOD_FUNC(rb_monitor_stop), 0);
1
+ #include "wdm.h"
2
+
3
+ #include "utils.h"
4
+ #include "entry.h"
5
+ #include "queue.h"
6
+ #include "monitor.h"
7
+
8
+ #include "rb_change.h"
9
+ #include "rb_monitor.h"
10
+
11
+ // ----------------------------------------------------------
12
+ // Global variables
13
+ // ----------------------------------------------------------
14
+
15
+ VALUE cWDM_Monitor;
16
+
17
+ VALUE eWDM_UnknownFlagError;
18
+
19
+ // ----------------------------------------------------------
20
+ // Cached variables
21
+ // ----------------------------------------------------------
22
+
23
+ static ID wdm_rb_sym_call;
24
+ static ID wdm_rb_sym_files;
25
+ static ID wdm_rb_sym_directories;
26
+ static ID wdm_rb_sym_attributes;
27
+ static ID wdm_rb_sym_size;
28
+ static ID wdm_rb_sym_last_write;
29
+ static ID wdm_rb_sym_last_access;
30
+ static ID wdm_rb_sym_creation;
31
+ static ID wdm_rb_sym_security;
32
+ static ID wdm_rb_sym_default;
33
+
34
+ // ---------------------------------------------------------
35
+ // Prototypes of static functions
36
+ // ---------------------------------------------------------
37
+
38
+ static void monitor_mark(LPVOID);
39
+ static void monitor_free(LPVOID);
40
+ static VALUE rb_monitor_alloc(VALUE);
41
+
42
+ static DWORD id_to_flag(ID);
43
+ static DWORD extract_flags_from_rb_array(VALUE);
44
+ static VALUE combined_watch(BOOL, int, VALUE*, VALUE);
45
+ static VALUE rb_monitor_watch(int, VALUE*, VALUE);
46
+
47
+ static VALUE rb_monitor_watch_recursively(int, VALUE*, VALUE);
48
+
49
+ static void CALLBACK handle_entry_change(DWORD, DWORD, LPOVERLAPPED);
50
+ static BOOL register_monitoring_entry(WDM_PEntry);
51
+ static DWORD WINAPI start_monitoring(LPVOID);
52
+
53
+ static VALUE wait_for_changes(LPVOID);
54
+ static void process_changes(WDM_PQueue);
55
+ static void stop_monitoring(LPVOID);
56
+ static VALUE rb_monitor_run_bang(VALUE);
57
+
58
+ static VALUE rb_monitor_stop(VALUE);
59
+
60
+ // ----------------------------------------------------------
61
+
62
+ static void
63
+ monitor_mark(LPVOID param)
64
+ {
65
+ WDM_PMonitor monitor;
66
+ WDM_PEntry entry;
67
+
68
+ monitor = (WDM_PMonitor)param;
69
+ entry = monitor->head;
70
+
71
+ while(entry != NULL) {
72
+ rb_gc_mark(entry->user_data->callback);
73
+ entry = entry->next;
74
+ }
75
+ }
76
+
77
+ static void
78
+ monitor_free(LPVOID param)
79
+ {
80
+ WDM_PMonitor monitor;
81
+ WDM_PEntry entry;
82
+
83
+ WDM_DEBUG("Freeing a monitor object!");
84
+
85
+ monitor = (WDM_PMonitor)param;
86
+ entry = monitor->head;
87
+
88
+ stop_monitoring(monitor); // If the monitor is already stopped, it would do nothing
89
+
90
+ while(entry != NULL) {
91
+ if ( entry->event_container.hEvent != NULL ) {
92
+ wdm_monitor_callback_param_free(
93
+ (WDM_PMonitorCallbackParam)entry->event_container.hEvent
94
+ );
95
+ }
96
+ entry = entry->next;
97
+ }
98
+
99
+ wdm_monitor_free(monitor);
100
+ }
101
+
102
+ static VALUE
103
+ rb_monitor_alloc(VALUE self)
104
+ {
105
+ WDM_DEBUG("--------------------------------");
106
+ WDM_DEBUG("Allocating a new monitor object!");
107
+ WDM_DEBUG("--------------------------------");
108
+
109
+ return Data_Wrap_Struct(self, monitor_mark, monitor_free, wdm_monitor_new());
110
+ }
111
+
112
+ static DWORD
113
+ id_to_flag(ID id)
114
+ {
115
+ if ( id == wdm_rb_sym_default ) return WDM_MONITOR_FLAGS_DEFAULT;
116
+
117
+ // TODO: Maybe reorder the if's in the frequency of use for better performance?
118
+ if ( id == wdm_rb_sym_files ) return FILE_NOTIFY_CHANGE_FILE_NAME;
119
+ if ( id == wdm_rb_sym_directories ) return FILE_NOTIFY_CHANGE_DIR_NAME;
120
+ if ( id == wdm_rb_sym_attributes ) return FILE_NOTIFY_CHANGE_ATTRIBUTES;
121
+ if ( id == wdm_rb_sym_size ) return FILE_NOTIFY_CHANGE_SIZE;
122
+ if ( id == wdm_rb_sym_last_write ) return FILE_NOTIFY_CHANGE_LAST_WRITE;
123
+ if ( id == wdm_rb_sym_last_access ) return FILE_NOTIFY_CHANGE_LAST_ACCESS;
124
+ if ( id == wdm_rb_sym_creation ) return FILE_NOTIFY_CHANGE_CREATION;
125
+ if ( id == wdm_rb_sym_security ) return FILE_NOTIFY_CHANGE_SECURITY;
126
+
127
+ rb_raise(eWDM_UnknownFlagError, "Unknown watch flag: ':%s'", rb_id2name(id));
128
+ }
129
+
130
+ static DWORD
131
+ extract_flags_from_rb_array(VALUE flags_array)
132
+ {
133
+ VALUE flag_symbol;
134
+ DWORD flags;
135
+
136
+ flags = 0;
137
+
138
+ while ( RARRAY_LEN(flags_array) != 0 ) {
139
+ flag_symbol = rb_ary_pop(flags_array);
140
+ Check_Type(flag_symbol, T_SYMBOL);
141
+ flags |= id_to_flag( SYM2ID(flag_symbol) );
142
+ }
143
+
144
+ return flags;
145
+ }
146
+
147
+ static VALUE
148
+ combined_watch(BOOL recursively, int argc, VALUE *argv, VALUE self)
149
+ {
150
+ WDM_PMonitor monitor;
151
+ WDM_PEntry entry;
152
+ int directory_letters_count;
153
+ VALUE directory, flags, os_encoded_directory;
154
+ BOOL running;
155
+
156
+ // TODO: Maybe raise a more user-friendly error?
157
+ rb_need_block();
158
+
159
+ Data_Get_Struct(self, WDM_Monitor, monitor);
160
+
161
+ EnterCriticalSection(&monitor->lock);
162
+ running = monitor->running;
163
+ LeaveCriticalSection(&monitor->lock);
164
+
165
+ if ( running ) {
166
+ rb_raise(eWDM_MonitorRunningError, "You can't watch new directories while the monitor is running!");
167
+ }
168
+
169
+ rb_scan_args(argc, argv, "1*", &directory, &flags);
170
+
171
+ Check_Type(directory, T_STRING);
172
+
173
+ entry = wdm_entry_new();
174
+ entry->user_data->watch_childeren = recursively;
175
+ entry->user_data->callback = rb_block_proc();
176
+ entry->user_data->flags = RARRAY_LEN(flags) == 0 ? WDM_MONITOR_FLAGS_DEFAULT : extract_flags_from_rb_array(flags);
177
+
178
+ // WTF Ruby source: The original code (file.c) uses the following macro to make sure that the encoding
179
+ // of the string is ASCII-compatible, but UTF-16LE (Windows default encoding) is not!!!
180
+ //
181
+ // FilePathValue(directory);
182
+
183
+ os_encoded_directory = rb_str_encode_ospath(directory);
184
+
185
+ // RSTRING_LEN can't be used because it would return the count of bytes the string uses in its encoding (like UTF-8).
186
+ // UTF-8 might use more than one byte for the char, which is not needed for WCHAR strings.
187
+ // Also, the result of MultiByteToWideChar _includes_ the NULL char at the end, which is not true for RSTRING.
188
+ //
189
+ // Example: 'C:\Users\Maher\Desktop\تجربة' with __ENCODING__ == UTF-8
190
+ // MultiByteToWideChar => 29 (28-char + null)
191
+ // RSTRING_LEN => 33 (23-char + 10-bytes for 5 Arabic letters which take 2 bytes each)
192
+ //
193
+ directory_letters_count = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(os_encoded_directory), -1, NULL, 0);
194
+
195
+ entry->user_data->dir = ALLOCA_N(WCHAR, directory_letters_count);
196
+
197
+ MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(os_encoded_directory), -1, entry->user_data->dir, directory_letters_count);
198
+
199
+ WDM_WDEBUG("New path to watch: '%s'", entry->user_data->dir);
200
+
201
+ entry->user_data->dir = wdm_utils_full_pathname(entry->user_data->dir);
202
+
203
+ if ( entry->user_data->dir == 0 ) {
204
+ wdm_entry_free(entry);
205
+ rb_raise(eWDM_Error, "Can't get the absolute path for the passed directory: '%s'!", RSTRING_PTR(directory));
206
+ }
207
+
208
+ if ( ! wdm_utils_unicode_is_directory(entry->user_data->dir) ) {
209
+ wdm_entry_free(entry);
210
+ rb_raise(eWDM_InvalidDirectoryError, "No such directory: '%s'!", RSTRING_PTR(directory));
211
+ }
212
+
213
+ entry->dir_handle = CreateFileW(
214
+ entry->user_data->dir, // pointer to the file name
215
+ FILE_LIST_DIRECTORY, // access (read/write) mode
216
+ FILE_SHARE_READ // share mode
217
+ | FILE_SHARE_WRITE
218
+ | FILE_SHARE_DELETE,
219
+ NULL, // security descriptor
220
+ OPEN_EXISTING, // how to create
221
+ FILE_FLAG_BACKUP_SEMANTICS
222
+ | FILE_FLAG_OVERLAPPED, // file attributes
223
+ NULL
224
+ );
225
+
226
+ if ( entry->dir_handle == INVALID_HANDLE_VALUE ) {
227
+ wdm_entry_free(entry);
228
+ rb_raise(eWDM_Error, "Can't watch directory: '%s'!", RSTRING_PTR(directory));
229
+ }
230
+
231
+ // Store a reference to the entry instead of an event as the event
232
+ // won't be used when using callbacks.
233
+ entry->event_container.hEvent = wdm_monitor_callback_param_new(monitor, entry);
234
+
235
+ wdm_monitor_update_head(monitor, entry);
236
+
237
+ WDM_WDEBUG("Watching directory: '%s'", entry->user_data->dir);
238
+
239
+ return Qnil;
240
+ }
241
+
242
+ static VALUE
243
+ rb_monitor_watch(int argc, VALUE *argv, VALUE self)
244
+ {
245
+ return combined_watch(FALSE, argc, argv, self);
246
+ }
247
+
248
+ static VALUE
249
+ rb_monitor_watch_recursively(int argc, VALUE *argv, VALUE self)
250
+ {
251
+ return combined_watch(TRUE, argc, argv, self);
252
+ }
253
+
254
+ static void CALLBACK
255
+ handle_entry_change(
256
+ DWORD err_code, // completion code
257
+ DWORD bytes_transfered, // number of bytes transferred
258
+ LPOVERLAPPED event_container // I/O information buffer
259
+ ) {
260
+ WDM_PMonitorCallbackParam param;
261
+ WDM_PQueueItem data_to_process;
262
+
263
+ if ( err_code == ERROR_OPERATION_ABORTED ) {
264
+ // Async operation was canceled. This shouldn't happen.
265
+ // TODO:
266
+ // 1. Maybe add a union in the queue for errors?
267
+ // 2. What's the best action when this happens?
268
+ WDM_DEBUG("Dir handler closed in the process callback!");
269
+ return;
270
+ }
271
+
272
+ if ( ! bytes_transfered ) {
273
+ WDM_DEBUG("Buffer overflow?! Changes are bigger than the buffer!");
274
+ return;
275
+ }
276
+
277
+ param = (WDM_PMonitorCallbackParam)event_container->hEvent;
278
+ data_to_process = wdm_queue_item_new(WDM_QUEUE_ITEM_TYPE_DATA);
279
+ data_to_process->data = wdm_queue_item_data_new();
280
+
281
+ WDM_WDEBUG("Change detected in '%s'", param->entry->user_data->dir);
282
+
283
+ data_to_process->data->user_data = param->entry->user_data;
284
+
285
+ // Copy change data to the backup buffer
286
+ memcpy(data_to_process->data->buffer, param->entry->buffer, bytes_transfered);
287
+
288
+ // Add the backup buffer to the change queue
289
+ wdm_queue_enqueue(param->monitor->changes, data_to_process);
290
+
291
+ // Resume watching the dir for changes
292
+ register_monitoring_entry(param->entry);
293
+
294
+ // Tell the processing thread to process the changes
295
+ if ( WaitForSingleObject(param->monitor->process_event, 0) != WAIT_OBJECT_0 ) { // Check if already signaled
296
+ SetEvent(param->monitor->process_event);
297
+ }
298
+ }
299
+
300
+ static BOOL
301
+ register_monitoring_entry(WDM_PEntry entry)
302
+ {
303
+ BOOL success;
304
+ DWORD bytes;
305
+ bytes = 0; // Not used because the process callback gets passed the written bytes
306
+
307
+ success = ReadDirectoryChangesW(
308
+ entry->dir_handle, // handle to directory
309
+ entry->buffer, // read results buffer
310
+ WDM_BUFFER_SIZE, // length of buffer
311
+ entry->user_data->watch_childeren, // monitoring option
312
+ entry->user_data->flags, // filter conditions
313
+ &bytes, // bytes returned
314
+ &entry->event_container, // overlapped buffer
315
+ &handle_entry_change // process callback
316
+ );
317
+
318
+ if ( ! success ) {
319
+ WDM_DEBUG("ReadDirectoryChangesW failed with error (%d): %s", GetLastError(), rb_w32_strerror(GetLastError()));
320
+ return FALSE;
321
+ }
322
+
323
+ return TRUE;
324
+ }
325
+
326
+ static DWORD WINAPI
327
+ start_monitoring(LPVOID param)
328
+ {
329
+ WDM_PMonitor monitor;
330
+ WDM_PEntry curr_entry;
331
+
332
+ monitor = (WDM_PMonitor)param;
333
+ curr_entry = monitor->head;
334
+
335
+ WDM_DEBUG("Starting the monitoring thread!");
336
+
337
+ while(curr_entry != NULL) {
338
+ if ( ! register_monitoring_entry(curr_entry) ) {
339
+ WDM_PQueueItem error_item;
340
+ int directory_bytes;
341
+ LPSTR multibyte_directory;
342
+
343
+ directory_bytes = WideCharToMultiByte(CP_UTF8, 0, curr_entry->user_data->dir, -1, NULL, 0, NULL, NULL);
344
+ multibyte_directory = ALLOCA_N(CHAR, directory_bytes);
345
+ WideCharToMultiByte(CP_UTF8, 0, curr_entry->user_data->dir, -1, multibyte_directory, directory_bytes, NULL, NULL);
346
+
347
+ error_item = wdm_queue_item_new(WDM_QUEUE_ITEM_TYPE_ERROR);
348
+ error_item->error = wdm_queue_item_error_new(
349
+ eWDM_UnwatchableDirectoryError, "Can't watch directory: '%s'!", multibyte_directory
350
+ );
351
+
352
+ wdm_queue_enqueue(monitor->changes, error_item);
353
+ SetEvent(monitor->process_event);
354
+ }
355
+
356
+ curr_entry = curr_entry->next;
357
+ }
358
+
359
+ while(monitor->running) {
360
+ // TODO: Is this the best way to do it?
361
+ if ( WaitForSingleObjectEx(monitor->stop_event, INFINITE, TRUE) == WAIT_OBJECT_0) {
362
+ WDM_DEBUG("Exiting the monitoring thread!");
363
+ ExitThread(0);
364
+ }
365
+ }
366
+
367
+ return 0;
368
+ }
369
+
370
+ static VALUE
371
+ wait_for_changes(LPVOID param)
372
+ {
373
+ HANDLE process_event;
374
+
375
+ process_event = (HANDLE)param;
376
+
377
+ return WaitForSingleObject(process_event, INFINITE) == WAIT_OBJECT_0 ? Qtrue : Qfalse;
378
+ }
379
+
380
+ static void
381
+ process_changes(WDM_PQueue changes)
382
+ {
383
+ WDM_PQueueItem item;
384
+ LPBYTE current_info_entry_offset;
385
+ PFILE_NOTIFY_INFORMATION info;
386
+ VALUE event;
387
+
388
+ WDM_DEBUG("---------------------------");
389
+ WDM_DEBUG("Process changes");
390
+ WDM_DEBUG("--------------------------");
391
+
392
+ while( ! wdm_queue_is_empty(changes) ) {
393
+ item = wdm_queue_dequeue(changes);
394
+
395
+ if ( item->type == WDM_QUEUE_ITEM_TYPE_ERROR ) {
396
+ rb_raise(item->error->exception_klass, item->error->message);
397
+ }
398
+ else {
399
+ current_info_entry_offset = (LPBYTE)item->data->buffer;
400
+
401
+ for(;;) {
402
+ info = (PFILE_NOTIFY_INFORMATION)current_info_entry_offset;
403
+ event = wdm_rb_change_new_from_notification(item->data->user_data->dir, info);
404
+
405
+ WDM_DEBUG("---------------------------");
406
+ WDM_DEBUG("Running user callback");
407
+ WDM_DEBUG("--------------------------");
408
+
409
+ rb_funcall(item->data->user_data->callback, wdm_rb_sym_call, 1, event);
410
+
411
+ WDM_DEBUG("---------------------------");
412
+
413
+ if ( ! info->NextEntryOffset ) break;
414
+
415
+ current_info_entry_offset += info->NextEntryOffset;
416
+ }
417
+ }
418
+
419
+ wdm_queue_item_free(item);
420
+ }
421
+ }
422
+
423
+ static void
424
+ stop_monitoring(LPVOID param)
425
+ {
426
+ BOOL already_stopped;
427
+ WDM_PMonitor monitor;
428
+ WDM_PEntry entry;
429
+
430
+ monitor = (WDM_PMonitor)param;
431
+ already_stopped = FALSE;
432
+
433
+ WDM_DEBUG("Stopping the monitor!");
434
+
435
+ EnterCriticalSection(&monitor->lock);
436
+ if ( ! monitor->running ) {
437
+ already_stopped = TRUE;
438
+ }
439
+ else {
440
+ monitor->running = FALSE;
441
+ }
442
+ LeaveCriticalSection(&monitor->lock);
443
+
444
+ if (already_stopped) {
445
+ WDM_DEBUG("Can't stop monitoring because it's already stopped (or it's never been started)!!");
446
+ return;
447
+ }
448
+
449
+ entry = monitor->head;
450
+
451
+ while(entry != NULL) {
452
+ CancelIo(entry->dir_handle); // Stop monitoring changes
453
+ entry = entry->next;
454
+ }
455
+
456
+ SetEvent(monitor->stop_event);
457
+ SetEvent(monitor->process_event); // The process code checks after the wait for an exit signal
458
+ WaitForSingleObject(monitor->monitoring_thread, 10000);
459
+ }
460
+
461
+ static VALUE
462
+ rb_monitor_run_bang(VALUE self)
463
+ {
464
+ BOOL already_running,
465
+ waiting_succeeded;
466
+ WDM_PMonitor monitor;
467
+
468
+ WDM_DEBUG("Running the monitor!");
469
+
470
+ Data_Get_Struct(self, WDM_Monitor, monitor);
471
+ already_running = FALSE;
472
+
473
+ EnterCriticalSection(&monitor->lock);
474
+ if ( monitor->running ) {
475
+ already_running = TRUE;
476
+ }
477
+ else {
478
+ monitor->running = TRUE;
479
+ }
480
+ LeaveCriticalSection(&monitor->lock);
481
+
482
+ if (already_running) {
483
+ WDM_DEBUG("Not doing anything because the monitor is already running!");
484
+ return Qnil;
485
+ }
486
+
487
+ // Reset events
488
+ ResetEvent(monitor->process_event);
489
+ ResetEvent(monitor->stop_event);
490
+
491
+ monitor->monitoring_thread = CreateThread(
492
+ NULL, // default security attributes
493
+ 0, // use default stack size
494
+ start_monitoring, // thread function name
495
+ monitor, // argument to thread function
496
+ 0, // use default creation flags
497
+ NULL // Ignore thread identifier
498
+ );
499
+
500
+ if ( monitor->monitoring_thread == NULL ) {
501
+ rb_raise(eWDM_Error, "Can't create a thread for the monitor!");
502
+ }
503
+
504
+ while ( monitor->running ) {
505
+
506
+ // Ruby 2.2 removed the 'rb_thread_blocking_region' function. Hence, we now need
507
+ // to check if the replacement function is defined and use it if it's available.
508
+ #ifdef HAVE_RB_THREAD_CALL_WITHOUT_GVL
509
+ waiting_succeeded = rb_thread_call_without_gvl(wait_for_changes, monitor->process_event, stop_monitoring, monitor);
510
+ #else
511
+ waiting_succeeded = rb_thread_blocking_region(wait_for_changes, monitor->process_event, stop_monitoring, monitor);
512
+ #endif
513
+
514
+ if ( waiting_succeeded == Qfalse ) {
515
+ rb_raise(eWDM_Error, "Failed while waiting for a change in the watched directories!");
516
+ }
517
+
518
+ if ( ! monitor->running ) {
519
+ wdm_queue_empty(monitor->changes);
520
+ return Qnil;
521
+ }
522
+
523
+ process_changes(monitor->changes);
524
+
525
+ if ( ! ResetEvent(monitor->process_event) ) {
526
+ rb_raise(eWDM_Error, "Couldn't reset system events to watch for changes!");
527
+ }
528
+ }
529
+
530
+ return Qnil;
531
+ }
532
+
533
+ static VALUE
534
+ rb_monitor_stop(VALUE self)
535
+ {
536
+ WDM_PMonitor monitor;
537
+
538
+ Data_Get_Struct(self, WDM_Monitor, monitor);
539
+
540
+ stop_monitoring(monitor);
541
+
542
+ WDM_DEBUG("Stopped the monitor!");
543
+
544
+ return Qnil;
545
+ }
546
+
547
+ void
548
+ wdm_rb_monitor_init()
549
+ {
550
+ WDM_DEBUG("Registering WDM::Monitor with Ruby!");
551
+
552
+ wdm_rb_sym_call = rb_intern("call");
553
+ wdm_rb_sym_files = rb_intern("files");
554
+ wdm_rb_sym_directories = rb_intern("directories");
555
+ wdm_rb_sym_attributes = rb_intern("attributes");
556
+ wdm_rb_sym_size = rb_intern("size");
557
+ wdm_rb_sym_last_write = rb_intern("last_write");
558
+ wdm_rb_sym_last_access = rb_intern("last_access");
559
+ wdm_rb_sym_creation = rb_intern("creation");
560
+ wdm_rb_sym_security = rb_intern("security");
561
+ wdm_rb_sym_default = rb_intern("default");
562
+
563
+ eWDM_MonitorRunningError = rb_define_class_under(mWDM, "MonitorRunningError", eWDM_Error);
564
+ eWDM_InvalidDirectoryError = rb_define_class_under(mWDM, "InvalidDirectoryError", eWDM_Error);
565
+ eWDM_UnknownFlagError = rb_define_class_under(mWDM, "UnknownFlagError", eWDM_Error);
566
+ eWDM_UnwatchableDirectoryError = rb_define_class_under(mWDM, "UnwatchableDirectoryError", eWDM_Error);
567
+
568
+ cWDM_Monitor = rb_define_class_under(mWDM, "Monitor", rb_cObject);
569
+
570
+ rb_define_alloc_func(cWDM_Monitor, rb_monitor_alloc);
571
+ rb_define_method(cWDM_Monitor, "watch", RUBY_METHOD_FUNC(rb_monitor_watch), -1);
572
+ rb_define_method(cWDM_Monitor, "watch_recursively", RUBY_METHOD_FUNC(rb_monitor_watch_recursively), -1);
573
+ rb_define_method(cWDM_Monitor, "run!", RUBY_METHOD_FUNC(rb_monitor_run_bang), 0);
574
+ rb_define_method(cWDM_Monitor, "stop", RUBY_METHOD_FUNC(rb_monitor_stop), 0);
571
575
  }