win32-changenotify 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,275 +1,246 @@
1
- require 'win32/event'
2
- require 'windows/nio'
3
- require 'windows/file'
4
- require 'windows/directory'
5
- require 'windows/unicode'
6
- require 'windows/msvcrt/buffer'
7
-
8
- # The Win32 module serves as a namespace only
9
- module Win32
10
-
11
- # The Win32::ChangeNotify class encapsulates filesystem change notifications
12
- class ChangeNotify < Ipc
13
- include Windows::NIO
14
- include Windows::File
15
- include Windows::Directory
16
- include Windows::Unicode
17
- include Windows::MSVCRT::Buffer
18
-
19
- # The version of the win32-changenotify library
20
- VERSION = '0.5.1'
21
-
22
- # Aliased constants
23
-
24
- # Filter: Attribute changes
25
- ATTRIBUTES = FILE_NOTIFY_CHANGE_ATTRIBUTES
26
-
27
- # Filter: Directory name changes
28
- DIR_NAME = FILE_NOTIFY_CHANGE_DIR_NAME
29
-
30
- # Filter: File name changes
31
- FILE_NAME = FILE_NOTIFY_CHANGE_FILE_NAME
32
-
33
- # Filter: Write changes
34
- LAST_WRITE = FILE_NOTIFY_CHANGE_LAST_WRITE
35
-
36
- # Filter: File security changes
37
- SECURITY = FILE_NOTIFY_CHANGE_SECURITY
38
-
39
- # Filter: File size changes
40
- SIZE = FILE_NOTIFY_CHANGE_SIZE
41
-
42
- # :stopdoc:
43
-
44
- # Private constants
45
-
46
- # File was added
47
- FILE_ACTION_ADDED = 0x00000001
48
-
49
- # File was deleted
50
- FILE_ACTION_REMOVED = 0x00000002
51
-
52
- # File was modified
53
- FILE_ACTION_MODIFIED = 0x00000003
54
-
55
- # File was renamed, old (original) name
56
- FILE_ACTION_RENAMED_OLD_NAME = 0x00000004
57
-
58
- # File was renamed, new (current) name
59
- FILE_ACTION_RENAMED_NEW_NAME = 0x00000005
60
-
61
- # Yielded by the ChangeNotify#wait method
62
- ChangeNotifyStruct = Struct.new('ChangeNotifyStruct', :action, :file_name)
63
-
64
- # :startdoc:
65
-
66
- # The path that was provided to the constructor
67
- attr_reader :path
68
-
69
- # The value of the filter (OR'd constants) passed to the constructor
70
- attr_reader :filter
71
-
72
- # Returns a new ChangeNotify object and places a monitor on +path+.
73
- # The +path+ argument may be a file or a directory.
74
- #
75
- # If +recursive is true and +path+ is a directory, then the monitor
76
- # applies to all subdirectories of +path+.
77
- #
78
- # The +filter+ tells the monitor what to watch for, such as file
79
- # changes, attribute changes, etc.
80
- #
81
- # If the +event+ option is specified, it must be a Win32::Event object.
82
- # It is then set as the event that will be set to the signaled state
83
- # when a notification has been completed.
84
- #
85
- # Yields itself if a block is provided, and automatically closes itself
86
- # when the block terminates.
87
- #
88
- def initialize(path, recursive, filter, event=nil)
89
- @path = path
90
- @recursive = recursive
91
- @filter = filter
92
- @overlap = 0.chr * 20 # OVERLAPPED struct
93
-
94
- # Because Win32API doesn't do type checking, we do it expicitly here.
95
- raise TypeError unless path.is_a?(String)
96
- raise TypeError unless [true, false].include?(recursive)
97
- raise TypeError unless filter.is_a?(Fixnum)
98
-
99
- if event
100
- raise TypeError unless event.respond_to?(:handle)
101
- @handle = event.handle
102
- else
103
- event = Win32::Event.new
104
- @handle = event.handle
105
- end
106
-
107
- @event = event
108
-
109
- @overlap[16,4] = [@handle].pack('L') # hEvent member
110
-
111
- super(@handle)
112
-
113
- if block_given?
114
- begin
115
- yield self
116
- ensure
117
- close
118
- end
119
- end
120
- end
121
-
122
- # Returns whether or not the ChangeNotify object is monitoring
123
- # subdirectories of the path being monitored.
124
- #
125
- def recursive?
126
- @recursive
127
- end
128
-
129
- alias ipc_wait wait
130
-
131
- # Waits up to 'seconds' for a notification to occur, or infinitely
132
- # if no value is specified.
133
- #
134
- # Yields an array of ChangeNotifyStruct's that contains two
135
- # members: file_name and action.
136
- #
137
- def wait(seconds = INFINITE)
138
- seconds *= 1000 unless seconds == INFINITE
139
-
140
- fni = 0.chr * 65536 # FILE_NOTIFY_INFORMATION struct buffer
141
- rbytes = [0].pack('L')
142
- qbytes = [0].pack('L')
143
-
144
- subtree = @recursive ? 1 : 0
145
- dir_handle = get_dir_handle(@path)
146
- comp_key = [12345].pack('L')
147
-
148
- begin
149
- comp_port = CreateIoCompletionPort(dir_handle, 0, comp_key, 0)
150
-
151
- if comp_port == 0
152
- raise Error, get_last_error
153
- end
154
-
155
- bool = ReadDirectoryChangesW(
156
- dir_handle,
157
- fni,
158
- fni.size,
159
- subtree,
160
- @filter,
161
- rbytes,
162
- @overlap,
163
- 0
164
- )
165
-
166
- unless bool
167
- raise Error, get_last_error
168
- end
169
-
170
- while true
171
- bool = GetQueuedCompletionStatus(
172
- comp_port,
173
- qbytes,
174
- comp_key,
175
- @overlap,
176
- seconds
177
- )
178
-
179
- unless bool
180
- raise Error, get_last_error
181
- end
182
-
183
- @signaled = true
184
- @event.signaled = true
185
-
186
- break if comp_key.unpack('L').first == 0
187
-
188
- yield get_file_action(fni) if block_given?
189
-
190
- bool = ReadDirectoryChangesW(
191
- dir_handle,
192
- fni,
193
- fni.size,
194
- subtree,
195
- @filter,
196
- rbytes,
197
- @overlap,
198
- 0
199
- )
200
-
201
- unless bool
202
- raise Error, get_last_error
203
- end
204
- end
205
- ensure
206
- CloseHandle(dir_handle)
207
- end
208
- end
209
-
210
- private
211
-
212
- # Returns an array of ChangeNotify structs, each containing a file name
213
- # and an action.
214
- #
215
- def get_file_action(fni2)
216
- fni = fni2.dup
217
- array = []
218
-
219
- while true
220
- int_action = fni[4,4].unpack('L')[0]
221
-
222
- str_action = 'unknown'
223
-
224
- case int_action
225
- when FILE_ACTION_ADDED
226
- str_action = 'added'
227
- when FILE_ACTION_REMOVED
228
- str_action = 'removed'
229
- when FILE_ACTION_MODIFIED
230
- str_action = 'modified'
231
- when FILE_ACTION_RENAMED_OLD_NAME
232
- str_action = 'renamed old name'
233
- when FILE_ACTION_RENAMED_NEW_NAME
234
- str_action = 'renamed new name'
235
- end
236
-
237
- len = fni[8,4].unpack('L').first # FileNameLength struct member
238
- file = fni[12,len] + "\0\0" # FileName struct member + null
239
- buf = 0.chr * 260
240
-
241
- WideCharToMultiByte(CP_ACP, 0, file, -1, buf, 260, 0, 0)
242
-
243
- file = File.join(@path, buf.unpack('A*')[0])
244
-
245
- struct = ChangeNotifyStruct.new(str_action, file)
246
- array.push(struct)
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