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.
@@ -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