wdm 0.0.2-x86-mingw32

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,531 @@
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
+ // WTF Ruby source: The original code (file.c) uses the following macro to make sure that the encoding
173
+ // of the string is ASCII-compatible, but UTF-16LE (Windows default encoding) is not!!!
174
+ //
175
+ // FilePathValue(directory);
176
+
177
+ os_encoded_directory = rb_str_encode_ospath(directory);
178
+
179
+ // RSTRING_LEN can't be used because it would return the count of bytes the string uses in its encoding (like UTF-8).
180
+ // UTF-8 might use more than one byte for the char, which is not needed for WCHAR strings.
181
+ // Also, the result of MultiByteToWideChar _includes_ the NULL char at the end, which is not true for RSTRING.
182
+ //
183
+ // Example: 'C:\Users\Maher\Desktop\تجربة' with __ENCODING__ == UTF-8
184
+ // MultiByteToWideChar => 29 (28-char + null)
185
+ // RSTRING_LEN => 33 (23-char + 10-bytes for 5 Arabic letters which take 2 bytes each)
186
+ //
187
+ directory_letters_count = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(os_encoded_directory), -1, NULL, 0);
188
+
189
+ entry->user_data->dir = ALLOCA_N(WCHAR, directory_letters_count);
190
+
191
+ MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(os_encoded_directory), -1, entry->user_data->dir, directory_letters_count);
192
+
193
+ WDM_WDEBUG("New path to watch: '%s'", entry->user_data->dir);
194
+
195
+ entry->user_data->dir = wdm_utils_full_pathname(entry->user_data->dir);
196
+
197
+ if ( entry->user_data->dir == 0 ) {
198
+ wdm_entry_free(entry);
199
+ rb_raise(eWDM_Error, "Can't get the absolute path for the passed directory: '%s'!", RSTRING_PTR(directory));
200
+ }
201
+
202
+ if ( ! wdm_utils_unicode_is_directory(entry->user_data->dir) ) {
203
+ wdm_entry_free(entry);
204
+ rb_raise(eWDM_InvalidDirectoryError, "No such directory: '%s'!", RSTRING_PTR(directory));
205
+ }
206
+
207
+ // Tell the GC to collect the tmp string
208
+ rb_str_resize(os_encoded_directory, 0);
209
+
210
+ entry->dir_handle = CreateFileW(
211
+ entry->user_data->dir, // pointer to the file name
212
+ FILE_LIST_DIRECTORY, // access (read/write) mode
213
+ FILE_SHARE_READ // share mode
214
+ | FILE_SHARE_WRITE
215
+ | FILE_SHARE_DELETE,
216
+ NULL, // security descriptor
217
+ OPEN_EXISTING, // how to create
218
+ FILE_FLAG_BACKUP_SEMANTICS
219
+ | FILE_FLAG_OVERLAPPED, // file attributes
220
+ NULL
221
+ );
222
+
223
+ if ( entry->dir_handle == INVALID_HANDLE_VALUE ) {
224
+ wdm_entry_free(entry);
225
+ rb_raise(eWDM_Error, "Can't watch directory: '%s'!", RSTRING_PTR(directory));
226
+ }
227
+
228
+ // Store a reference to the entry instead of an event as the event
229
+ // won't be used when using callbacks.
230
+ entry->event_container.hEvent = wdm_monitor_callback_param_new(monitor, entry);
231
+
232
+ wdm_monitor_update_head(monitor, entry);
233
+
234
+ WDM_WDEBUG("Watching directory: '%s'", entry->user_data->dir);
235
+
236
+ return Qnil;
237
+ }
238
+
239
+ static VALUE
240
+ rb_monitor_watch(int argc, VALUE *argv, VALUE self) {
241
+ return combined_watch(FALSE, argc, argv, self);
242
+ }
243
+
244
+ static VALUE
245
+ rb_monitor_watch_recursively(int argc, VALUE *argv, VALUE self) {
246
+ return combined_watch(TRUE, argc, argv, self);
247
+ }
248
+
249
+ static void CALLBACK
250
+ handle_entry_change(
251
+ DWORD err_code, // completion code
252
+ DWORD bytes_transfered, // number of bytes transferred
253
+ LPOVERLAPPED event_container // I/O information buffer
254
+ ) {
255
+ WDM_PMonitorCallbackParam param;
256
+ WDM_PQueueItem data_to_process;
257
+
258
+ if ( err_code == ERROR_OPERATION_ABORTED ) {
259
+ // Async operation was canceld. This shouldn't happen.
260
+ // TODO:
261
+ // 1. Maybe add a union in the queue for errors?
262
+ // 2. What's the best action when this happens?
263
+ WDM_DEBUG("Dir handler closed in the process callback!");
264
+ return;
265
+ }
266
+
267
+ if ( ! bytes_transfered ) {
268
+ WDM_DEBUG("Buffer overflow?! Changes are bigger than the buffer!");
269
+ return;
270
+ }
271
+
272
+ param = (WDM_PMonitorCallbackParam)event_container->hEvent;
273
+ data_to_process = wdm_queue_item_new();
274
+
275
+ WDM_WDEBUG("Change detected in '%s'", param->entry->user_data->dir);
276
+
277
+ data_to_process->user_data = param->entry->user_data;
278
+
279
+ // Copy change data to the backup buffer
280
+ memcpy(data_to_process->buffer, param->entry->buffer, bytes_transfered);
281
+
282
+ // Add the backup buffer to the change queue
283
+ wdm_queue_enqueue(param->monitor->changes, data_to_process);
284
+
285
+ // Resume watching the dir for changes
286
+ register_monitoring_entry(param->entry);
287
+
288
+ // Tell the processing thread to process the changes
289
+ if ( WaitForSingleObject( param->monitor->process_event, 0) != WAIT_OBJECT_0 ) { // Check if already signaled
290
+ SetEvent(param->monitor->process_event);
291
+ }
292
+ }
293
+
294
+ static void
295
+ register_monitoring_entry(WDM_PEntry entry) {
296
+ BOOL success;
297
+ DWORD bytes;
298
+ bytes = 0; // Not used because the process callback gets passed the written bytes
299
+
300
+ success = ReadDirectoryChangesW(
301
+ entry->dir_handle, // handle to directory
302
+ entry->buffer, // read results buffer
303
+ WDM_BUFFER_SIZE, // length of buffer
304
+ entry->user_data->watch_childeren, // monitoring option
305
+ entry->user_data->flags, // filter conditions
306
+ &bytes, // bytes returned
307
+ &entry->event_container, // overlapped buffer
308
+ &handle_entry_change // process callback
309
+ );
310
+
311
+ if ( ! success ) {
312
+ // --------------------------
313
+ // TODO: Handle error!
314
+ // --------------------------
315
+ WDM_DEBUG("Failed registering directory: '%s'!", entry->user_data->dir);
316
+ }
317
+ }
318
+
319
+ static DWORD WINAPI
320
+ start_monitoring(LPVOID param) {
321
+ WDM_PMonitor monitor;
322
+ WDM_PEntry curr_entry;
323
+
324
+ monitor = (WDM_PMonitor)param;
325
+ curr_entry = monitor->head;
326
+
327
+ WDM_DEBUG("Starting the monitoring thread!");
328
+
329
+ while(curr_entry != NULL) {
330
+ register_monitoring_entry(curr_entry);
331
+ curr_entry = curr_entry->next;
332
+ }
333
+
334
+ while(monitor->running) {
335
+ // TODO: Is this the best way to do it?
336
+ if ( WaitForSingleObjectEx(monitor->stop_event, INFINITE, TRUE) == WAIT_OBJECT_0) {
337
+ WDM_DEBUG("Exiting the monitoring thread!");
338
+ ExitThread(0);
339
+ }
340
+ }
341
+
342
+ return 0;
343
+ }
344
+
345
+ static VALUE
346
+ wait_for_changes(LPVOID param) {
347
+ HANDLE process_event;
348
+
349
+ process_event = (HANDLE)param;
350
+
351
+ return WaitForSingleObject(process_event, INFINITE) == WAIT_OBJECT_0 ? Qtrue : Qfalse;
352
+ }
353
+
354
+ static void
355
+ process_changes(WDM_PQueue changes) {
356
+ WDM_PQueueItem item;
357
+ LPBYTE current_info_entry_offset;
358
+ PFILE_NOTIFY_INFORMATION info;
359
+ VALUE event;
360
+
361
+ WDM_DEBUG("---------------------------");
362
+ WDM_DEBUG("Process changes");
363
+ WDM_DEBUG("--------------------------");
364
+
365
+ while( ! wdm_queue_is_empty(changes) ) {
366
+ item = wdm_queue_dequeue(changes);
367
+ current_info_entry_offset = (LPBYTE)item->buffer;
368
+
369
+ for(;;) {
370
+ info = (PFILE_NOTIFY_INFORMATION)current_info_entry_offset;
371
+ event = wdm_rb_change_new_from_notification(item->user_data->dir, info);
372
+
373
+ WDM_DEBUG("---------------------------");
374
+ WDM_DEBUG("Running user callback");
375
+ WDM_DEBUG("--------------------------");
376
+
377
+ rb_funcall(item->user_data->callback, wdm_rb_sym_call, 1, event);
378
+
379
+ WDM_DEBUG("---------------------------");
380
+
381
+ if ( ! info->NextEntryOffset ) break;
382
+
383
+ current_info_entry_offset += info->NextEntryOffset;
384
+ }
385
+
386
+ wdm_queue_item_free(item);
387
+ }
388
+ }
389
+
390
+ static void
391
+ stop_monitoring(LPVOID param) {
392
+ BOOL already_stopped;
393
+ WDM_PMonitor monitor;
394
+ WDM_PEntry entry;
395
+
396
+ monitor = (WDM_PMonitor)param;
397
+ already_stopped = FALSE;
398
+
399
+ WDM_DEBUG("Stopping the monitor!");
400
+
401
+ EnterCriticalSection(&monitor->lock);
402
+ if ( ! monitor->running ) {
403
+ already_stopped = TRUE;
404
+ }
405
+ else {
406
+ monitor->running = FALSE;
407
+ }
408
+ LeaveCriticalSection(&monitor->lock);
409
+
410
+ if (already_stopped) {
411
+ WDM_DEBUG("Can't stop monitoring because it's already stopped (or it's never been started)!!");
412
+ return;
413
+ }
414
+
415
+ entry = monitor->head;
416
+
417
+ while(entry != NULL) {
418
+ CancelIo(entry->dir_handle); // Stop monitoring changes
419
+ entry = entry->next;
420
+ }
421
+
422
+ SetEvent(monitor->stop_event);
423
+ SetEvent(monitor->process_event); // The process code checks after the wait for an exit signal
424
+ WaitForSingleObject(monitor->monitoring_thread, 10000);
425
+ }
426
+
427
+ static VALUE
428
+ rb_monitor_run_bang(VALUE self) {
429
+ DWORD thread_id;
430
+ BOOL already_running,
431
+ waiting_succeeded;
432
+ WDM_PMonitor monitor;
433
+
434
+ WDM_DEBUG("Running the monitor!");
435
+
436
+ Data_Get_Struct(self, WDM_Monitor, monitor);
437
+ already_running = FALSE;
438
+
439
+ EnterCriticalSection(&monitor->lock);
440
+ if ( monitor->running ) {
441
+ already_running = TRUE;
442
+ }
443
+ else {
444
+ monitor->running = TRUE;
445
+ }
446
+ LeaveCriticalSection(&monitor->lock);
447
+
448
+ if (already_running) {
449
+ WDM_DEBUG("Not doing anything because the monitor is already running!");
450
+ return Qnil;
451
+ }
452
+
453
+ // Reset events
454
+ ResetEvent(monitor->process_event);
455
+ ResetEvent(monitor->stop_event);
456
+
457
+ monitor->monitoring_thread = CreateThread(
458
+ NULL, // default security attributes
459
+ 0, // use default stack size
460
+ start_monitoring, // thread function name
461
+ monitor, // argument to thread function
462
+ 0, // use default creation flags
463
+ &thread_id // returns the thread identifier
464
+ );
465
+
466
+ if ( monitor->monitoring_thread == NULL ) {
467
+ rb_raise(eWDM_Error, "Can't create a thread for the monitor!");
468
+ }
469
+
470
+ while ( monitor->running ) {
471
+ waiting_succeeded = rb_thread_blocking_region(wait_for_changes, monitor->process_event, stop_monitoring, monitor);
472
+
473
+ if ( waiting_succeeded == Qfalse ) {
474
+ rb_raise(eWDM_Error, "Failed while waiting for a change in the watched directories!");
475
+ }
476
+
477
+ if ( ! monitor->running ) {
478
+ wdm_queue_empty(monitor->changes);
479
+ return Qnil;
480
+ }
481
+
482
+ process_changes(monitor->changes);
483
+
484
+ if ( ! ResetEvent(monitor->process_event) ) {
485
+ rb_raise(eWDM_Error, "Couldn't reset system events to watch for changes!");
486
+ }
487
+ }
488
+
489
+ return Qnil;
490
+ }
491
+
492
+ static VALUE
493
+ rb_monitor_stop(VALUE self) {
494
+ WDM_PMonitor monitor;
495
+
496
+ Data_Get_Struct(self, WDM_Monitor, monitor);
497
+
498
+ stop_monitoring(monitor);
499
+
500
+ WDM_DEBUG("Stopped the monitor!");
501
+
502
+ return Qnil;
503
+ }
504
+
505
+ void
506
+ wdm_rb_monitor_init() {
507
+ WDM_DEBUG("Registering WDM::Monitor with Ruby!");
508
+
509
+ wdm_rb_sym_call = rb_intern("call");
510
+ wdm_rb_sym_files = rb_intern("files");
511
+ wdm_rb_sym_directories = rb_intern("directories");
512
+ wdm_rb_sym_attributes = rb_intern("attributes");
513
+ wdm_rb_sym_size = rb_intern("size");
514
+ wdm_rb_sym_last_write = rb_intern("last_write");
515
+ wdm_rb_sym_last_access = rb_intern("last_access");
516
+ wdm_rb_sym_creation = rb_intern("creation");
517
+ wdm_rb_sym_security = rb_intern("security");
518
+ wdm_rb_sym_default = rb_intern("default");
519
+
520
+ eWDM_MonitorRunningError = rb_define_class_under(mWDM, "MonitorRunningError", eWDM_Error);
521
+ eWDM_InvalidDirectoryError = rb_define_class_under(mWDM, "InvalidDirectoryError", eWDM_Error);
522
+ eWDM_UnknownFlagError = rb_define_class_under(mWDM, "UnknownFlagError", eWDM_Error);
523
+
524
+ cWDM_Monitor = rb_define_class_under(mWDM, "Monitor", rb_cObject);
525
+
526
+ rb_define_alloc_func(cWDM_Monitor, rb_monitor_alloc);
527
+ rb_define_method(cWDM_Monitor, "watch", RUBY_METHOD_FUNC(rb_monitor_watch), -1);
528
+ rb_define_method(cWDM_Monitor, "watch_recursively", RUBY_METHOD_FUNC(rb_monitor_watch_recursively), -1);
529
+ rb_define_method(cWDM_Monitor, "run!", RUBY_METHOD_FUNC(rb_monitor_run_bang), 0);
530
+ rb_define_method(cWDM_Monitor, "stop", RUBY_METHOD_FUNC(rb_monitor_stop), 0);
531
+ }