wdm 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,50 @@
1
+ #include <Windows.h>
2
+
3
+ #include "entry.h"
4
+
5
+ #ifndef WDM_QUEUE_H
6
+ #define WDM_QUEUE_H
7
+
8
+ #ifdef __cplusplus
9
+ extern "C" {
10
+ #endif // __cplusplus
11
+
12
+ // ---------------------------------------------------------
13
+ // Types
14
+ // ---------------------------------------------------------
15
+
16
+ typedef struct WDM_QueueItem {
17
+ WDM_PEntryUserData user_data;
18
+ BYTE buffer[WDM_BUFFER_SIZE];
19
+ struct WDM_QueueItem* previous;
20
+ struct WDM_QueueItem* next;
21
+ } WDM_QueueItem, *WDM_PQueueItem;
22
+
23
+ typedef struct WDM_Queue {
24
+ CRITICAL_SECTION lock;
25
+ WDM_PQueueItem front;
26
+ WDM_PQueueItem rear;
27
+ } WDM_Queue, *WDM_PQueue;
28
+
29
+ // ---------------------------------------------------------
30
+ // Prototypes
31
+ // ---------------------------------------------------------
32
+
33
+ WDM_PQueueItem wdm_queue_item_new();
34
+ void wdm_queue_item_free(WDM_PQueueItem);
35
+
36
+ WDM_PQueue wdm_queue_new();
37
+ void wdm_queue_free(WDM_PQueue);
38
+
39
+ void wdm_queue_enqueue(WDM_PQueue, WDM_PQueueItem);
40
+ WDM_PQueueItem wdm_queue_dequeue(WDM_PQueue);
41
+ void wdm_queue_empty(WDM_PQueue);
42
+ BOOL wdm_queue_is_empty(WDM_PQueue);
43
+
44
+ // ---------------------------------------------------------
45
+
46
+ #ifdef __cplusplus
47
+ }
48
+ #endif // __cplusplus
49
+
50
+ #endif // WDM_QUEUE_H
@@ -0,0 +1,185 @@
1
+ #include <stdlib.h>
2
+ #include <wchar.h>
3
+
4
+ #include "wdm.h"
5
+
6
+ #include "utils.h"
7
+
8
+ #include "rb_change.h"
9
+
10
+ // ---------------------------------------------------------
11
+ // Internal constants
12
+ // ---------------------------------------------------------
13
+
14
+ // The _wsplitpat constants account for two NULL chars, so substract 1 because we only need one!
15
+ #define WDM_MAX_FILENAME (_MAX_FNAME + _MAX_EXT - 1)
16
+
17
+ // ----------------------------------------------------------
18
+ // Global variables
19
+ // ----------------------------------------------------------
20
+
21
+ VALUE cWDM_Change;
22
+
23
+ static ID wdm_rb_sym_at_path;
24
+ static ID wdm_rb_sym_at_type;
25
+ static ID wdm_rb_sym_added;
26
+ static ID wdm_rb_sym_modified;
27
+ static ID wdm_rb_sym_removed;
28
+ static ID wdm_rb_sym_renamed_old_file;
29
+ static ID wdm_rb_sym_renamed_new_file;
30
+
31
+ // ----------------------------------------------------------
32
+ // Prototypes of static functions
33
+ // ----------------------------------------------------------
34
+
35
+ static VALUE extract_absolute_path_from_notification(const LPWSTR, const PFILE_NOTIFY_INFORMATION);
36
+ static VALUE extract_change_type_from_notification(const PFILE_NOTIFY_INFORMATION);
37
+
38
+ // ----------------------------------------------------------
39
+
40
+ // TODO:
41
+ // 1. this function uses a lot of 'alloca' calls, which AFAIK is not recommeneded! Can this be avoided?
42
+ // 2. all wcscat calls can be done faster with memcpy, but is it worth sacrificing the readability?
43
+ static VALUE
44
+ extract_absolute_path_from_notification(const LPWSTR base_dir, const PFILE_NOTIFY_INFORMATION info) {
45
+ LPWSTR buffer, absolute_filepath;
46
+ WCHAR file[_MAX_FNAME], ext[_MAX_EXT], filename[WDM_MAX_FILENAME];
47
+ DWORD filename_len, absolute_filepath_len;
48
+ LPSTR multibyte_filepath;
49
+ int multibyte_filepath_buffer_size;
50
+ VALUE path;
51
+
52
+ filename_len = info->FileNameLength/sizeof(WCHAR);
53
+
54
+ // The file in the 'info' struct is NOT null-terminated, so add 1 extra char to the allocation
55
+ buffer = ALLOCA_N(WCHAR, filename_len + 1);
56
+
57
+ memcpy(buffer, info->FileName, info->FileNameLength);
58
+
59
+ // Null-terminate the string
60
+ buffer[filename_len] = L'\0';
61
+
62
+ WDM_WDEBUG("change in: '%s'", buffer);
63
+
64
+ absolute_filepath_len = wcslen(base_dir) + filename_len;
65
+ absolute_filepath = ALLOCA_N(WCHAR, absolute_filepath_len + 1); // 1 for NULL
66
+ absolute_filepath[0] = L'\0';
67
+
68
+ wcscat(absolute_filepath, base_dir);
69
+ wcscat(absolute_filepath, buffer);
70
+
71
+ WDM_WDEBUG("absolute path is: '%s'", absolute_filepath);
72
+
73
+ _wsplitpath(buffer, NULL, NULL, file, ext);
74
+
75
+ // TODO: Extracting the file name from 'buffer' is only needed when watching sub-dirs
76
+ filename[0] = L'\0';
77
+ if ( file[0] != L'\0' ) wcscat(filename, file);
78
+ if ( ext[0] != L'\0' ) wcscat(filename, ext);
79
+
80
+ WDM_WDEBUG("filename: '%s'", filename);
81
+
82
+ filename_len = wcslen(filename);
83
+
84
+ // The maximum length of an 8.3 filename is twelve, including the dot.
85
+ if (filename_len <= 12 && wcschr(filename, L'~'))
86
+ {
87
+ LPWSTR unicode_absolute_filepath;
88
+ WCHAR absolute_long_filepath[WDM_MAX_WCHAR_LONG_PATH];
89
+
90
+ unicode_absolute_filepath = ALLOCA_N(WCHAR, absolute_filepath_len + 4 + 1); // 4 for "\\?\" and 1 for NULL
91
+
92
+ unicode_absolute_filepath[0] = L'\0';
93
+ wcscat(unicode_absolute_filepath, L"\\\\?\\");
94
+ wcscat(unicode_absolute_filepath, absolute_filepath);
95
+
96
+ // Convert to the long filename form. Unfortunately, this
97
+ // does not work for deletions, so it's an imperfect fix.
98
+ if (GetLongPathNameW(unicode_absolute_filepath, absolute_long_filepath, WDM_MAX_WCHAR_LONG_PATH) != 0) {
99
+ absolute_filepath = absolute_long_filepath + 4; // Skip first 4 pointers of "\\?\"
100
+ absolute_filepath_len = wcslen(absolute_filepath);
101
+ WDM_WDEBUG("Short path converted to long: '%s'", absolute_filepath);
102
+ }
103
+ else {
104
+ WDM_DEBUG("Can't convert short path to long: '%s'", rb_w32_strerror(GetLastError()));
105
+ }
106
+ }
107
+
108
+ // The convention in Ruby is to use forward-slashes to seprarate dirs on all platforms.
109
+ wdm_utils_convert_back_to_forward_slashes(absolute_filepath, absolute_filepath_len + 1);
110
+
111
+ // Convert the path from WCHAR to multibyte CHAR to use it in a ruby string
112
+ multibyte_filepath_buffer_size =
113
+ WideCharToMultiByte(CP_UTF8, 0, absolute_filepath, absolute_filepath_len + 1, NULL, 0, NULL, NULL);
114
+
115
+ multibyte_filepath = ALLOCA_N(CHAR, multibyte_filepath_buffer_size);
116
+
117
+ if ( 0 == WideCharToMultiByte(CP_UTF8, 0, absolute_filepath, absolute_filepath_len + 1,
118
+ multibyte_filepath, multibyte_filepath_buffer_size, NULL, NULL) ) {
119
+ rb_raise(eWDM_Error, "Failed to add the change file path to the event!");
120
+ }
121
+
122
+ WDM_DEBUG("will report change in: '%s'", multibyte_filepath);
123
+
124
+ path = rb_enc_str_new(multibyte_filepath,
125
+ multibyte_filepath_buffer_size - 1, // -1 because this func takes the chars count, not bytes count
126
+ wdm_rb_enc_utf8);
127
+
128
+ OBJ_TAINT(path);
129
+
130
+ return path;
131
+ }
132
+
133
+ static VALUE
134
+ extract_change_type_from_notification(const PFILE_NOTIFY_INFORMATION info) {
135
+ ID type;
136
+
137
+ switch(info->Action) {
138
+ case FILE_ACTION_ADDED: type = wdm_rb_sym_added; break;
139
+ case FILE_ACTION_REMOVED: type = wdm_rb_sym_removed; break;
140
+ case FILE_ACTION_MODIFIED: type = wdm_rb_sym_modified; break;
141
+ case FILE_ACTION_RENAMED_OLD_NAME: type = wdm_rb_sym_renamed_old_file; break;
142
+ case FILE_ACTION_RENAMED_NEW_NAME: type = wdm_rb_sym_renamed_new_file; break;
143
+ default:
144
+ rb_raise(eWDM_Error, "Unknown change happened to a file in a watched directory!");
145
+ }
146
+
147
+ #if WDM_DEBUG_ENABLED // Used to avoid the func call when in release mode
148
+ WDM_DEBUG("change type: '%s'", rb_id2name(type));
149
+ #endif
150
+
151
+ return ID2SYM(type);
152
+ }
153
+
154
+ VALUE
155
+ wdm_rb_change_new_from_notification(const LPWSTR base_dir, const PFILE_NOTIFY_INFORMATION info) {
156
+ VALUE change;
157
+
158
+ change = rb_class_new_instance(0, NULL, cWDM_Change);
159
+
160
+ // Set '@type' to the change type
161
+ rb_ivar_set(change, wdm_rb_sym_at_type, extract_change_type_from_notification(info));
162
+
163
+ // Set '@path' to the absolute path of the changed file/directory
164
+ rb_ivar_set(change, wdm_rb_sym_at_path, extract_absolute_path_from_notification(base_dir, info));
165
+
166
+ return change;
167
+ }
168
+
169
+ void
170
+ wdm_rb_change_init() {
171
+ WDM_DEBUG("Registering WDM::Event with Ruby!");
172
+
173
+ wdm_rb_sym_at_path = rb_intern("@path");
174
+ wdm_rb_sym_at_type = rb_intern("@type");
175
+ wdm_rb_sym_added = rb_intern("added");
176
+ wdm_rb_sym_modified = rb_intern("modified");
177
+ wdm_rb_sym_removed = rb_intern("removed");
178
+ wdm_rb_sym_renamed_old_file = rb_intern("renamed_old_file");
179
+ wdm_rb_sym_renamed_new_file = rb_intern("renamed_new_file");
180
+
181
+ cWDM_Change = rb_define_class_under(mWDM, "Change", rb_cObject);
182
+
183
+ rb_define_attr(cWDM_Change, "path", 1, 0);
184
+ rb_define_attr(cWDM_Change, "type", 1, 0);
185
+ }
@@ -0,0 +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
+
28
+ #endif // WDM_RB_CHANGE_H
@@ -0,0 +1,534 @@
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 void 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
+ WDM_PMonitor monitor;
65
+ WDM_PEntry entry;
66
+
67
+ monitor = (WDM_PMonitor)param;
68
+ entry = monitor->head;
69
+
70
+ while(entry != NULL) {
71
+ rb_gc_mark(entry->user_data->callback);
72
+ entry = entry->next;
73
+ }
74
+ }
75
+
76
+ static void
77
+ monitor_free(LPVOID param) {
78
+ WDM_PMonitor monitor;
79
+ WDM_PEntry entry;
80
+
81
+ WDM_DEBUG("Freeing a monitor object!");
82
+
83
+ monitor = (WDM_PMonitor)param;
84
+ entry = monitor->head;
85
+
86
+ stop_monitoring(monitor); // If the monitor is already stopped, it would do nothing
87
+
88
+ while(entry != NULL) {
89
+ if ( entry->event_container.hEvent != NULL ) {
90
+ wdm_monitor_callback_param_free(
91
+ (WDM_PMonitorCallbackParam)entry->event_container.hEvent
92
+ );
93
+ }
94
+ entry = entry->next;
95
+ }
96
+
97
+ wdm_monitor_free(monitor);
98
+ }
99
+
100
+ static VALUE
101
+ rb_monitor_alloc(VALUE self) {
102
+ WDM_DEBUG("--------------------------------");
103
+ WDM_DEBUG("Allocating a new monitor object!");
104
+ WDM_DEBUG("--------------------------------");
105
+
106
+ return Data_Wrap_Struct(self, monitor_mark, monitor_free, wdm_monitor_new());
107
+ }
108
+
109
+ static DWORD
110
+ id_to_flag(ID id) {
111
+ if ( id == wdm_rb_sym_default ) return WDM_MONITOR_FLAGS_DEFAULT;
112
+
113
+ // TODO: Maybe reorder the if's in the frequentie of use for better performance?
114
+ if ( id == wdm_rb_sym_files ) return FILE_NOTIFY_CHANGE_FILE_NAME;
115
+ if ( id == wdm_rb_sym_directories ) return FILE_NOTIFY_CHANGE_DIR_NAME;
116
+ if ( id == wdm_rb_sym_attributes ) return FILE_NOTIFY_CHANGE_ATTRIBUTES;
117
+ if ( id == wdm_rb_sym_size ) return FILE_NOTIFY_CHANGE_SIZE;
118
+ if ( id == wdm_rb_sym_last_write ) return FILE_NOTIFY_CHANGE_LAST_WRITE;
119
+ if ( id == wdm_rb_sym_last_access ) return FILE_NOTIFY_CHANGE_LAST_ACCESS;
120
+ if ( id == wdm_rb_sym_creation ) return FILE_NOTIFY_CHANGE_CREATION;
121
+ if ( id == wdm_rb_sym_security ) return FILE_NOTIFY_CHANGE_SECURITY;
122
+
123
+ rb_raise(eWDM_UnknownFlagError, "Unknown watch flag: ':%s'", rb_id2name(id));
124
+ }
125
+
126
+ static DWORD
127
+ extract_flags_from_rb_array(VALUE flags_array) {
128
+ VALUE flag_symbol;
129
+ DWORD flags;
130
+
131
+ flags = 0;
132
+
133
+ while ( RARRAY_LEN(flags_array) != 0 ) {
134
+ flag_symbol = rb_ary_pop(flags_array);
135
+ Check_Type(flag_symbol, T_SYMBOL);
136
+ flags |= id_to_flag( SYM2ID(flag_symbol) );
137
+ }
138
+
139
+ return flags;
140
+ }
141
+
142
+ static VALUE
143
+ combined_watch(BOOL recursively, int argc, VALUE *argv, VALUE self) {
144
+ WDM_PMonitor monitor;
145
+ WDM_PEntry entry;
146
+ int directory_letters_count;
147
+ VALUE directory, flags, os_encoded_directory;
148
+ BOOL running;
149
+
150
+ // TODO: Maybe raise a more user-friendly error?
151
+ rb_need_block();
152
+
153
+ Data_Get_Struct(self, WDM_Monitor, monitor);
154
+
155
+ EnterCriticalSection(&monitor->lock);
156
+ running = monitor->running;
157
+ LeaveCriticalSection(&monitor->lock);
158
+
159
+ if ( running ) {
160
+ rb_raise(eWDM_MonitorRunningError, "You can't watch new directories while the monitor is running!");
161
+ }
162
+
163
+ rb_scan_args(argc, argv, "1*", &directory, &flags);
164
+
165
+ Check_Type(directory, T_STRING);
166
+
167
+ entry = wdm_entry_new();
168
+ entry->user_data->watch_childeren = recursively;
169
+ entry->user_data->callback = rb_block_proc();
170
+ entry->user_data->flags = RARRAY_LEN(flags) == 0 ? WDM_MONITOR_FLAGS_DEFAULT : extract_flags_from_rb_array(flags);
171
+
172
+ // Prevent the GC from collecting the block until we are done.
173
+ rb_global_variable(&entry->user_data->callback);
174
+
175
+ // WTF Ruby source: The original code (file.c) uses the following macro to make sure that the encoding
176
+ // of the string is ASCII-compatible, but UTF-16LE (Windows default encoding) is not!!!
177
+ //
178
+ // FilePathValue(directory);
179
+
180
+ os_encoded_directory = rb_str_encode_ospath(directory);
181
+
182
+ // RSTRING_LEN can't be used because it would return the count of bytes the string uses in its encoding (like UTF-8).
183
+ // UTF-8 might use more than one byte for the char, which is not needed for WCHAR strings.
184
+ // Also, the result of MultiByteToWideChar _includes_ the NULL char at the end, which is not true for RSTRING.
185
+ //
186
+ // Example: 'C:\Users\Maher\Desktop\تجربة' with __ENCODING__ == UTF-8
187
+ // MultiByteToWideChar => 29 (28-char + null)
188
+ // RSTRING_LEN => 33 (23-char + 10-bytes for 5 Arabic letters which take 2 bytes each)
189
+ //
190
+ directory_letters_count = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(os_encoded_directory), -1, NULL, 0);
191
+
192
+ entry->user_data->dir = ALLOCA_N(WCHAR, directory_letters_count);
193
+
194
+ MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(os_encoded_directory), -1, entry->user_data->dir, directory_letters_count);
195
+
196
+ WDM_WDEBUG("New path to watch: '%s'", entry->user_data->dir);
197
+
198
+ entry->user_data->dir = wdm_utils_full_pathname(entry->user_data->dir);
199
+
200
+ if ( entry->user_data->dir == 0 ) {
201
+ wdm_entry_free(entry);
202
+ rb_raise(eWDM_Error, "Can't get the absolute path for the passed directory: '%s'!", RSTRING_PTR(directory));
203
+ }
204
+
205
+ if ( ! wdm_utils_unicode_is_directory(entry->user_data->dir) ) {
206
+ wdm_entry_free(entry);
207
+ rb_raise(eWDM_InvalidDirectoryError, "No such directory: '%s'!", RSTRING_PTR(directory));
208
+ }
209
+
210
+ // Tell the GC to collect the tmp string
211
+ rb_str_resize(os_encoded_directory, 0);
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
+ return combined_watch(FALSE, argc, argv, self);
245
+ }
246
+
247
+ static VALUE
248
+ rb_monitor_watch_recursively(int argc, VALUE *argv, VALUE self) {
249
+ return combined_watch(TRUE, argc, argv, self);
250
+ }
251
+
252
+ static void CALLBACK
253
+ handle_entry_change(
254
+ DWORD err_code, // completion code
255
+ DWORD bytes_transfered, // number of bytes transferred
256
+ LPOVERLAPPED event_container // I/O information buffer
257
+ ) {
258
+ WDM_PMonitorCallbackParam param;
259
+ WDM_PQueueItem data_to_process;
260
+
261
+ if ( err_code == ERROR_OPERATION_ABORTED ) {
262
+ // Async operation was canceld. This shouldn't happen.
263
+ // TODO:
264
+ // 1. Maybe add a union in the queue for errors?
265
+ // 2. What's the best action when this happens?
266
+ WDM_DEBUG("Dir handler closed in the process callback!");
267
+ return;
268
+ }
269
+
270
+ if ( ! bytes_transfered ) {
271
+ WDM_DEBUG("Buffer overflow?! Changes are bigger than the buffer!");
272
+ return;
273
+ }
274
+
275
+ param = (WDM_PMonitorCallbackParam)event_container->hEvent;
276
+ data_to_process = wdm_queue_item_new();
277
+
278
+ WDM_WDEBUG("Change detected in '%s'", param->entry->user_data->dir);
279
+
280
+ data_to_process->user_data = param->entry->user_data;
281
+
282
+ // Copy change data to the backup buffer
283
+ memcpy(data_to_process->buffer, param->entry->buffer, bytes_transfered);
284
+
285
+ // Add the backup buffer to the change queue
286
+ wdm_queue_enqueue(param->monitor->changes, data_to_process);
287
+
288
+ // Resume watching the dir for changes
289
+ register_monitoring_entry(param->entry);
290
+
291
+ // Tell the processing thread to process the changes
292
+ if ( WaitForSingleObject( param->monitor->process_event, 0) != WAIT_OBJECT_0 ) { // Check if already signaled
293
+ SetEvent(param->monitor->process_event);
294
+ }
295
+ }
296
+
297
+ static void
298
+ register_monitoring_entry(WDM_PEntry entry) {
299
+ BOOL success;
300
+ DWORD bytes;
301
+ bytes = 0; // Not used because the process callback gets passed the written bytes
302
+
303
+ success = ReadDirectoryChangesW(
304
+ entry->dir_handle, // handle to directory
305
+ entry->buffer, // read results buffer
306
+ WDM_BUFFER_SIZE, // length of buffer
307
+ entry->user_data->watch_childeren, // monitoring option
308
+ entry->user_data->flags, // filter conditions
309
+ &bytes, // bytes returned
310
+ &entry->event_container, // overlapped buffer
311
+ &handle_entry_change // process callback
312
+ );
313
+
314
+ if ( ! success ) {
315
+ // --------------------------
316
+ // TODO: Handle error!
317
+ // --------------------------
318
+ WDM_DEBUG("Failed registering directory: '%s'!", entry->user_data->dir);
319
+ }
320
+ }
321
+
322
+ static DWORD WINAPI
323
+ start_monitoring(LPVOID param) {
324
+ WDM_PMonitor monitor;
325
+ WDM_PEntry curr_entry;
326
+
327
+ monitor = (WDM_PMonitor)param;
328
+ curr_entry = monitor->head;
329
+
330
+ WDM_DEBUG("Starting the monitoring thread!");
331
+
332
+ while(curr_entry != NULL) {
333
+ register_monitoring_entry(curr_entry);
334
+ curr_entry = curr_entry->next;
335
+ }
336
+
337
+ while(monitor->running) {
338
+ // TODO: Is this the best way to do it?
339
+ if ( WaitForSingleObjectEx(monitor->stop_event, INFINITE, TRUE) == WAIT_OBJECT_0) {
340
+ WDM_DEBUG("Exiting the monitoring thread!");
341
+ ExitThread(0);
342
+ }
343
+ }
344
+
345
+ return 0;
346
+ }
347
+
348
+ static VALUE
349
+ wait_for_changes(LPVOID param) {
350
+ HANDLE process_event;
351
+
352
+ process_event = (HANDLE)param;
353
+
354
+ return WaitForSingleObject(process_event, INFINITE) == WAIT_OBJECT_0 ? Qtrue : Qfalse;
355
+ }
356
+
357
+ static void
358
+ process_changes(WDM_PQueue changes) {
359
+ WDM_PQueueItem item;
360
+ LPBYTE current_info_entry_offset;
361
+ PFILE_NOTIFY_INFORMATION info;
362
+ VALUE event;
363
+
364
+ WDM_DEBUG("---------------------------");
365
+ WDM_DEBUG("Process changes");
366
+ WDM_DEBUG("--------------------------");
367
+
368
+ while( ! wdm_queue_is_empty(changes) ) {
369
+ item = wdm_queue_dequeue(changes);
370
+ current_info_entry_offset = (LPBYTE)item->buffer;
371
+
372
+ for(;;) {
373
+ info = (PFILE_NOTIFY_INFORMATION)current_info_entry_offset;
374
+ event = wdm_rb_change_new_from_notification(item->user_data->dir, info);
375
+
376
+ WDM_DEBUG("---------------------------");
377
+ WDM_DEBUG("Running user callback");
378
+ WDM_DEBUG("--------------------------");
379
+
380
+ rb_funcall(item->user_data->callback, wdm_rb_sym_call, 1, event);
381
+
382
+ WDM_DEBUG("---------------------------");
383
+
384
+ if ( ! info->NextEntryOffset ) break;
385
+
386
+ current_info_entry_offset += info->NextEntryOffset;
387
+ }
388
+
389
+ wdm_queue_item_free(item);
390
+ }
391
+ }
392
+
393
+ static void
394
+ stop_monitoring(LPVOID param) {
395
+ BOOL already_stopped;
396
+ WDM_PMonitor monitor;
397
+ WDM_PEntry entry;
398
+
399
+ monitor = (WDM_PMonitor)param;
400
+ already_stopped = FALSE;
401
+
402
+ WDM_DEBUG("Stopping the monitor!");
403
+
404
+ EnterCriticalSection(&monitor->lock);
405
+ if ( ! monitor->running ) {
406
+ already_stopped = TRUE;
407
+ }
408
+ else {
409
+ monitor->running = FALSE;
410
+ }
411
+ LeaveCriticalSection(&monitor->lock);
412
+
413
+ if (already_stopped) {
414
+ WDM_DEBUG("Can't stop monitoring because it's already stopped (or it's never been started)!!");
415
+ return;
416
+ }
417
+
418
+ entry = monitor->head;
419
+
420
+ while(entry != NULL) {
421
+ CancelIo(entry->dir_handle); // Stop monitoring changes
422
+ entry = entry->next;
423
+ }
424
+
425
+ SetEvent(monitor->stop_event);
426
+ SetEvent(monitor->process_event); // The process code checks after the wait for an exit signal
427
+ WaitForSingleObject(monitor->monitoring_thread, 10000);
428
+ }
429
+
430
+ static VALUE
431
+ rb_monitor_run_bang(VALUE self) {
432
+ DWORD thread_id;
433
+ BOOL already_running,
434
+ waiting_succeeded;
435
+ WDM_PMonitor monitor;
436
+
437
+ WDM_DEBUG("Running the monitor!");
438
+
439
+ Data_Get_Struct(self, WDM_Monitor, monitor);
440
+ already_running = FALSE;
441
+
442
+ EnterCriticalSection(&monitor->lock);
443
+ if ( monitor->running ) {
444
+ already_running = TRUE;
445
+ }
446
+ else {
447
+ monitor->running = TRUE;
448
+ }
449
+ LeaveCriticalSection(&monitor->lock);
450
+
451
+ if (already_running) {
452
+ WDM_DEBUG("Not doing anything because the monitor is already running!");
453
+ return;
454
+ }
455
+
456
+ // Reset events
457
+ ResetEvent(monitor->process_event);
458
+ ResetEvent(monitor->stop_event);
459
+
460
+ monitor->monitoring_thread = CreateThread(
461
+ NULL, // default security attributes
462
+ 0, // use default stack size
463
+ start_monitoring, // thread function name
464
+ monitor, // argument to thread function
465
+ 0, // use default creation flags
466
+ &thread_id // returns the thread identifier
467
+ );
468
+
469
+ if ( monitor->monitoring_thread == NULL ) {
470
+ rb_raise(eWDM_Error, "Can't create a thread for the monitor!");
471
+ }
472
+
473
+ while ( monitor->running ) {
474
+ waiting_succeeded = rb_thread_blocking_region(wait_for_changes, monitor->process_event, stop_monitoring, monitor);
475
+
476
+ if ( waiting_succeeded == Qfalse ) {
477
+ rb_raise(eWDM_Error, "Failed while waiting for a change in the watched directories!");
478
+ }
479
+
480
+ if ( ! monitor->running ) {
481
+ wdm_queue_empty(monitor->changes);
482
+ return;
483
+ }
484
+
485
+ process_changes(monitor->changes);
486
+
487
+ if ( ! ResetEvent(monitor->process_event) ) {
488
+ rb_raise(eWDM_Error, "Couldn't reset system events to watch for changes!");
489
+ }
490
+ }
491
+
492
+ return Qnil;
493
+ }
494
+
495
+ static VALUE
496
+ rb_monitor_stop(VALUE self) {
497
+ WDM_PMonitor monitor;
498
+
499
+ Data_Get_Struct(self, WDM_Monitor, monitor);
500
+
501
+ stop_monitoring(monitor);
502
+
503
+ WDM_DEBUG("Stopped the monitor!");
504
+
505
+ return Qnil;
506
+ }
507
+
508
+ void
509
+ wdm_rb_monitor_init() {
510
+ WDM_DEBUG("Registering WDM::Monitor with Ruby!");
511
+
512
+ wdm_rb_sym_call = rb_intern("call");
513
+ wdm_rb_sym_files = rb_intern("files");
514
+ wdm_rb_sym_directories = rb_intern("directories");
515
+ wdm_rb_sym_attributes = rb_intern("attributes");
516
+ wdm_rb_sym_size = rb_intern("size");
517
+ wdm_rb_sym_last_write = rb_intern("last_write");
518
+ wdm_rb_sym_last_access = rb_intern("last_access");
519
+ wdm_rb_sym_creation = rb_intern("creation");
520
+ wdm_rb_sym_security = rb_intern("security");
521
+ wdm_rb_sym_default = rb_intern("default");
522
+
523
+ eWDM_MonitorRunningError = rb_define_class_under(mWDM, "MonitorRunningError", eWDM_Error);
524
+ eWDM_InvalidDirectoryError = rb_define_class_under(mWDM, "InvalidDirectoryError", eWDM_Error);
525
+ eWDM_UnknownFlagError = rb_define_class_under(mWDM, "UnknownFlagError", eWDM_Error);
526
+
527
+ cWDM_Monitor = rb_define_class_under(mWDM, "Monitor", rb_cObject);
528
+
529
+ rb_define_alloc_func(cWDM_Monitor, rb_monitor_alloc);
530
+ rb_define_method(cWDM_Monitor, "watch", RUBY_METHOD_FUNC(rb_monitor_watch), -1);
531
+ rb_define_method(cWDM_Monitor, "watch_recursively", RUBY_METHOD_FUNC(rb_monitor_watch_recursively), -1);
532
+ rb_define_method(cWDM_Monitor, "run!", RUBY_METHOD_FUNC(rb_monitor_run_bang), 0);
533
+ rb_define_method(cWDM_Monitor, "stop", RUBY_METHOD_FUNC(rb_monitor_stop), 0);
534
+ }