wdm 0.0.2-mingw32
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.
- data/LICENSE +22 -0
- data/README.md +58 -0
- data/ext/wdm/entry.c +67 -0
- data/ext/wdm/entry.h +47 -0
- data/ext/wdm/extconf.rb +9 -0
- data/ext/wdm/memory.c +27 -0
- data/ext/wdm/memory.h +32 -0
- data/ext/wdm/monitor.c +70 -0
- data/ext/wdm/monitor.h +50 -0
- data/ext/wdm/queue.c +108 -0
- data/ext/wdm/queue.h +50 -0
- data/ext/wdm/rb_change.c +185 -0
- data/ext/wdm/rb_change.h +28 -0
- data/ext/wdm/rb_monitor.c +531 -0
- data/ext/wdm/rb_monitor.h +39 -0
- data/ext/wdm/utils.c +60 -0
- data/ext/wdm/utils.h +25 -0
- data/ext/wdm/wdm.c +45 -0
- data/ext/wdm/wdm.h +66 -0
- data/ext/wdm/wdm.sln +20 -0
- data/ext/wdm/wdm.vcxproj +104 -0
- data/ext/wdm/wdm.vcxproj.filters +74 -0
- metadata +167 -0
data/ext/wdm/queue.h
ADDED
@@ -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
|
data/ext/wdm/rb_change.c
ADDED
@@ -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
|
+
}
|
data/ext/wdm/rb_change.h
ADDED
@@ -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
|
+
}
|