win32-changenotify 0.5.1 → 0.6.0
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.
- checksums.yaml +7 -0
- data/CHANGES +70 -66
- data/MANIFEST +11 -11
- data/README +91 -97
- data/Rakefile +26 -17
- data/examples/example_win32_changenotify.rb +36 -35
- data/lib/win32/changenotify.rb +246 -275
- data/lib/win32/changenotify/constants.rb +28 -0
- data/lib/win32/changenotify/functions.rb +60 -0
- data/lib/win32/changenotify/structs.rb +26 -0
- data/test/test_win32_changenotify.rb +93 -70
- data/win32-changenotify.gemspec +28 -28
- metadata +87 -43
data/lib/win32/changenotify.rb
CHANGED
@@ -1,275 +1,246 @@
|
|
1
|
-
require 'win32/event'
|
2
|
-
|
3
|
-
require '
|
4
|
-
require '
|
5
|
-
require '
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
#
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
break if fni[0,4].unpack('L')[0] == 0
|
248
|
-
fni = fni[fni[0,4].unpack('L').first .. -1] # Next offset
|
249
|
-
break if fni.nil?
|
250
|
-
end
|
251
|
-
|
252
|
-
array
|
253
|
-
end
|
254
|
-
|
255
|
-
# Returns a HANDLE to the directory +path+ created via CreateFile().
|
256
|
-
#
|
257
|
-
def get_dir_handle(path)
|
258
|
-
handle = CreateFile(
|
259
|
-
path,
|
260
|
-
FILE_LIST_DIRECTORY,
|
261
|
-
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
|
262
|
-
0,
|
263
|
-
OPEN_EXISTING,
|
264
|
-
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
|
265
|
-
0
|
266
|
-
)
|
267
|
-
|
268
|
-
if handle == INVALID_HANDLE_VALUE
|
269
|
-
raise Error, get_last_error
|
270
|
-
end
|
271
|
-
|
272
|
-
handle
|
273
|
-
end
|
274
|
-
end
|
275
|
-
end
|
1
|
+
require 'win32/event'
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), 'changenotify', 'constants')
|
4
|
+
require File.join(File.dirname(__FILE__), 'changenotify', 'functions')
|
5
|
+
require File.join(File.dirname(__FILE__), 'changenotify', 'structs')
|
6
|
+
|
7
|
+
# The Win32 module serves as a namespace only
|
8
|
+
module Win32
|
9
|
+
|
10
|
+
# The Win32::ChangeNotify class encapsulates filesystem change notifications
|
11
|
+
class ChangeNotify < Ipc
|
12
|
+
include Windows::Constants
|
13
|
+
include Windows::Structs
|
14
|
+
include Windows::Functions
|
15
|
+
|
16
|
+
extend Windows::Functions
|
17
|
+
|
18
|
+
# The version of the win32-changenotify library
|
19
|
+
VERSION = '0.6.0'
|
20
|
+
|
21
|
+
# Filter: Attribute changes
|
22
|
+
ATTRIBUTES = FILE_NOTIFY_CHANGE_ATTRIBUTES
|
23
|
+
|
24
|
+
# Filter: Directory name changes
|
25
|
+
DIR_NAME = FILE_NOTIFY_CHANGE_DIR_NAME
|
26
|
+
|
27
|
+
# Filter: File name changes
|
28
|
+
FILE_NAME = FILE_NOTIFY_CHANGE_FILE_NAME
|
29
|
+
|
30
|
+
# Filter: Write changes
|
31
|
+
LAST_WRITE = FILE_NOTIFY_CHANGE_LAST_WRITE
|
32
|
+
|
33
|
+
# Filter: File security changes
|
34
|
+
SECURITY = FILE_NOTIFY_CHANGE_SECURITY
|
35
|
+
|
36
|
+
# Filter: File size changes
|
37
|
+
SIZE = FILE_NOTIFY_CHANGE_SIZE
|
38
|
+
|
39
|
+
# :stopdoc:
|
40
|
+
|
41
|
+
# Yielded by the ChangeNotify#wait method
|
42
|
+
ChangeNotifyStruct = Struct.new('ChangeNotifyStruct', :action, :file_name)
|
43
|
+
|
44
|
+
# :startdoc:
|
45
|
+
|
46
|
+
# The path that was provided to the constructor
|
47
|
+
attr_reader :path
|
48
|
+
|
49
|
+
# The value of the filter (OR'd constants) passed to the constructor
|
50
|
+
attr_reader :filter
|
51
|
+
|
52
|
+
# Returns a new ChangeNotify object and places a monitor on +path+.
|
53
|
+
# The +path+ argument may be a file or a directory.
|
54
|
+
#
|
55
|
+
# If +recursive is true and +path+ is a directory, then the monitor
|
56
|
+
# applies to all subdirectories of +path+.
|
57
|
+
#
|
58
|
+
# The +filter+ tells the monitor what to watch for, such as file
|
59
|
+
# changes, attribute changes, etc.
|
60
|
+
#
|
61
|
+
# If the +event+ option is specified, it must be a Win32::Event object.
|
62
|
+
# It is then set as the event that will be set to the signaled state
|
63
|
+
# when a notification has been completed.
|
64
|
+
#
|
65
|
+
# Yields itself if a block is provided, and automatically closes itself
|
66
|
+
# when the block terminates.
|
67
|
+
#
|
68
|
+
def initialize(path, recursive, filter, event=nil)
|
69
|
+
@path = path
|
70
|
+
@recursive = recursive
|
71
|
+
@filter = filter
|
72
|
+
@overlap = OVERLAPPED.new
|
73
|
+
|
74
|
+
# Because Win32API doesn't do type checking, we do it expicitly here.
|
75
|
+
raise TypeError unless path.is_a?(String)
|
76
|
+
raise TypeError unless [true, false].include?(recursive)
|
77
|
+
raise TypeError unless filter.is_a?(Fixnum)
|
78
|
+
|
79
|
+
if event
|
80
|
+
raise TypeError unless event.respond_to?(:handle)
|
81
|
+
@handle = event.handle
|
82
|
+
else
|
83
|
+
event = Win32::Event.new
|
84
|
+
@handle = event.handle
|
85
|
+
end
|
86
|
+
|
87
|
+
@event = event
|
88
|
+
|
89
|
+
@overlap[:hEvent] = @handle
|
90
|
+
|
91
|
+
super(@handle)
|
92
|
+
|
93
|
+
if block_given?
|
94
|
+
begin
|
95
|
+
yield self
|
96
|
+
ensure
|
97
|
+
close
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns whether or not the ChangeNotify object is monitoring
|
103
|
+
# subdirectories of the path being monitored.
|
104
|
+
#
|
105
|
+
def recursive?
|
106
|
+
@recursive
|
107
|
+
end
|
108
|
+
|
109
|
+
alias ipc_wait wait
|
110
|
+
|
111
|
+
# Waits up to 'seconds' for a notification to occur, or infinitely
|
112
|
+
# if no value is specified.
|
113
|
+
#
|
114
|
+
# Yields an array of ChangeNotifyStruct's that contains two
|
115
|
+
# members: file_name and action.
|
116
|
+
#
|
117
|
+
def wait(seconds = INFINITE)
|
118
|
+
seconds *= 1000 unless seconds == INFINITE
|
119
|
+
|
120
|
+
fni_ptr = FFI::MemoryPointer.new(FILE_NOTIFY_INFORMATION, 4096)
|
121
|
+
rbytes = FFI::MemoryPointer.new(:ulong)
|
122
|
+
qbytes = FFI::MemoryPointer.new(:ulong)
|
123
|
+
|
124
|
+
dir_handle = get_dir_handle(@path)
|
125
|
+
|
126
|
+
comp_key = FFI::MemoryPointer.new(:ulong)
|
127
|
+
comp_key.write_ulong(12345)
|
128
|
+
|
129
|
+
begin
|
130
|
+
comp_port = CreateIoCompletionPort(dir_handle, 0, comp_key, 0)
|
131
|
+
|
132
|
+
if comp_port == 0
|
133
|
+
raise SystemCallError.new('CreateIoCompletionPort', FFI.errno)
|
134
|
+
end
|
135
|
+
|
136
|
+
bool = ReadDirectoryChangesW(
|
137
|
+
dir_handle,
|
138
|
+
fni_ptr,
|
139
|
+
fni_ptr.size,
|
140
|
+
@recursive,
|
141
|
+
@filter,
|
142
|
+
rbytes,
|
143
|
+
@overlap,
|
144
|
+
nil
|
145
|
+
)
|
146
|
+
|
147
|
+
raise_windows_error('ReadDirectoryChangesW') unless bool
|
148
|
+
|
149
|
+
while true
|
150
|
+
bool = GetQueuedCompletionStatus(
|
151
|
+
comp_port,
|
152
|
+
qbytes,
|
153
|
+
comp_key,
|
154
|
+
@overlap,
|
155
|
+
seconds
|
156
|
+
)
|
157
|
+
|
158
|
+
raise_windows_error('GetQueuedCompletionStatus') unless bool
|
159
|
+
|
160
|
+
@signaled = true
|
161
|
+
@event.signaled = true
|
162
|
+
|
163
|
+
break if comp_key.read_ulong == 0
|
164
|
+
|
165
|
+
yield get_file_action(fni_ptr) if block_given?
|
166
|
+
|
167
|
+
bool = ReadDirectoryChangesW(
|
168
|
+
dir_handle,
|
169
|
+
fni_ptr,
|
170
|
+
fni_ptr.size,
|
171
|
+
@recursive,
|
172
|
+
@filter,
|
173
|
+
rbytes,
|
174
|
+
@overlap,
|
175
|
+
nil
|
176
|
+
)
|
177
|
+
|
178
|
+
raise_windows_error('ReadDirectoryChangesW') unless bool
|
179
|
+
end
|
180
|
+
ensure
|
181
|
+
CloseHandle(dir_handle)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
private
|
186
|
+
|
187
|
+
# Returns an array of ChangeNotify structs, each containing a file name
|
188
|
+
# and an action.
|
189
|
+
#
|
190
|
+
def get_file_action(fni_ptr2)
|
191
|
+
fni_ptr = fni_ptr2.dup # Will segfault otherwise
|
192
|
+
array = []
|
193
|
+
|
194
|
+
while true
|
195
|
+
str_action = 'unknown'
|
196
|
+
fni = FILE_NOTIFY_INFORMATION.new(fni_ptr)
|
197
|
+
|
198
|
+
case fni[:Action]
|
199
|
+
when FILE_ACTION_ADDED
|
200
|
+
str_action = 'added'
|
201
|
+
when FILE_ACTION_REMOVED
|
202
|
+
str_action = 'removed'
|
203
|
+
when FILE_ACTION_MODIFIED
|
204
|
+
str_action = 'modified'
|
205
|
+
when FILE_ACTION_RENAMED_OLD_NAME
|
206
|
+
str_action = 'renamed old name'
|
207
|
+
when FILE_ACTION_RENAMED_NEW_NAME
|
208
|
+
str_action = 'renamed new name'
|
209
|
+
end
|
210
|
+
|
211
|
+
len = fni[:FileNameLength]
|
212
|
+
file = (fni[:FileName].to_ptr.read_string(len) + "\0\0").force_encoding('UTF-16LE')
|
213
|
+
file.encode!(Encoding.default_external)
|
214
|
+
|
215
|
+
struct = ChangeNotifyStruct.new(str_action, file)
|
216
|
+
array.push(struct)
|
217
|
+
|
218
|
+
break if fni[:NextEntryOffset] == 0
|
219
|
+
fni_ptr += fni[:NextEntryOffset]
|
220
|
+
break if fni.null?
|
221
|
+
end
|
222
|
+
|
223
|
+
array
|
224
|
+
end
|
225
|
+
|
226
|
+
# Returns a HANDLE to the directory +path+ created via CreateFile().
|
227
|
+
#
|
228
|
+
def get_dir_handle(path)
|
229
|
+
handle = CreateFileA(
|
230
|
+
path,
|
231
|
+
FILE_LIST_DIRECTORY,
|
232
|
+
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
|
233
|
+
nil,
|
234
|
+
OPEN_EXISTING,
|
235
|
+
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
|
236
|
+
0
|
237
|
+
)
|
238
|
+
|
239
|
+
if handle == INVALID_HANDLE_VALUE
|
240
|
+
raise_windows_error('CreateFileA')
|
241
|
+
end
|
242
|
+
|
243
|
+
handle
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|