win32-changejournal 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +31 -0
- data/MANIFEST +11 -0
- data/README +76 -0
- data/ext/win32/changejournal.c +487 -0
- data/lib/win32/changejournal.so +0 -0
- data/test/tc_changejournal.rb +60 -0
- metadata +62 -0
data/CHANGES
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
== 0.3.2 - 27-Apr-2008
|
2
|
+
* Fixed RubyForge bug #10555 (bignum too big to convert into long).
|
3
|
+
* Lots of internal reorganization, partially in preparation for the
|
4
|
+
pure Ruby version that will be version 0.4.0.
|
5
|
+
* Added a Rakefile with tasks for testing, building and installation.
|
6
|
+
* Added a gemspec file.
|
7
|
+
* Merged the documentation that was in the doc/changejournal.txt file
|
8
|
+
into the README.
|
9
|
+
* Added a gemspec
|
10
|
+
|
11
|
+
== 0.3.1 - 2-Jan-2006
|
12
|
+
* Internal cleanup only (now compiles cleanly with -W3).
|
13
|
+
|
14
|
+
== 0.3.0 - 30-Dec-2005
|
15
|
+
* The Struct::ChangeJournalStruct now contains a third member: path. This
|
16
|
+
is the full path to the file which was modified. Thanks go to Stephen
|
17
|
+
Haberman for the idea, and Heesob for the implementation.
|
18
|
+
* More tests added.
|
19
|
+
* Example script updated.
|
20
|
+
|
21
|
+
== 0.2.0 - 24-Apr-2005
|
22
|
+
* Modified ChangeJournal#wait to return an array of structs, rather than a
|
23
|
+
single struct, because multiple events can be associated with a single
|
24
|
+
action.
|
25
|
+
* Removed the changejournal.rd file. The changejournal.txt file is rdoc
|
26
|
+
friendly, so you can generate html from that if you wish.
|
27
|
+
* Fixed the release date for 0.1.0.
|
28
|
+
* Minor doc and test changes or updates.
|
29
|
+
|
30
|
+
== 0.1.0 - 13-Feb-2005
|
31
|
+
* Initial release
|
data/MANIFEST
ADDED
data/README
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
== Description
|
2
|
+
A class for monitoring events related to files and directories on NTFS.
|
3
|
+
|
4
|
+
== Installation
|
5
|
+
rake install (non-gem)
|
6
|
+
rake gem-install (gem)
|
7
|
+
|
8
|
+
== Synopsis
|
9
|
+
require 'win32/changejournal'
|
10
|
+
include Win32
|
11
|
+
|
12
|
+
# Indefinitely wait for a change in 'C:\' and any of its
|
13
|
+
# subdirectories. Print the file and action affected.
|
14
|
+
#
|
15
|
+
cj = ChangeJournal.new('C:\')
|
16
|
+
|
17
|
+
cj.wait{ |array|
|
18
|
+
array.each{ |info|
|
19
|
+
p info.file_name
|
20
|
+
p info.action
|
21
|
+
p info.path
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
c.delete
|
26
|
+
|
27
|
+
== Class Methods
|
28
|
+
ChangeJournal.new(drive)
|
29
|
+
Returns a new ChangeJournal object and places a monitor on +drive+.
|
30
|
+
|
31
|
+
== Instance Methods
|
32
|
+
ChangeJournal#wait(num_seconds=INFINITE)
|
33
|
+
ChangeJournal#wait(num_seconds=INFINITE){ |arr| ... }
|
34
|
+
|
35
|
+
Waits up to 'num_seconds' for a notification to occur, or infinitely if
|
36
|
+
no value is specified.
|
37
|
+
|
38
|
+
If a block is provided, yields an array of ChangeJournalStruct's that
|
39
|
+
contains three members: file_name, action, and path. An array is returned
|
40
|
+
because multiple actions can be associated with a single event.
|
41
|
+
|
42
|
+
== Constants
|
43
|
+
=== Standard constants
|
44
|
+
VERSION
|
45
|
+
Returns the current version number of this library as a String.
|
46
|
+
|
47
|
+
== Notes
|
48
|
+
Based on what the MSDN documentation says, this library requires NTFS, and
|
49
|
+
should be preferred on that filesystem. On FAT filesystems, you should
|
50
|
+
use the win32-changenotify library instead.
|
51
|
+
|
52
|
+
== Acknowledgements
|
53
|
+
This class was originally based on the CJTest module by Jeffrey
|
54
|
+
Cooperstein & Jeffrey Richter.
|
55
|
+
|
56
|
+
== Future Plans
|
57
|
+
Add a method for iterating over all change records.
|
58
|
+
|
59
|
+
== Known Bugs
|
60
|
+
None that I know of. Please log any bug reports on the RubyForge
|
61
|
+
project page at http://www.rubyforge.net/projects/win32utils
|
62
|
+
|
63
|
+
== License
|
64
|
+
Ruby's
|
65
|
+
|
66
|
+
== Copyright
|
67
|
+
(C) 2003-2008 Daniel J. Berger, All Rights Reserved
|
68
|
+
|
69
|
+
== Warranty
|
70
|
+
This library is provided "as is" and without any express or
|
71
|
+
implied warranties, including, without limitation, the implied
|
72
|
+
warranties of merchantability and fitness for a particular purpose.
|
73
|
+
|
74
|
+
== Authors
|
75
|
+
Park Heesob
|
76
|
+
Daniel J. Berger
|
@@ -0,0 +1,487 @@
|
|
1
|
+
/* changejournal.c */
|
2
|
+
#include "ruby.h"
|
3
|
+
#include <windows.h>
|
4
|
+
#include <winioctl.h>
|
5
|
+
#include "changejournal.h"
|
6
|
+
|
7
|
+
// Function Prototypes
|
8
|
+
void InitialzeForMonitoring(ChangeJournalStruct *ptr);
|
9
|
+
|
10
|
+
/* This is a helper function that opens a handle to the volume specified
|
11
|
+
* by the cDriveLetter parameter.
|
12
|
+
*/
|
13
|
+
HANDLE Open(TCHAR cDriveLetter, DWORD dwAccess, BOOL fAsyncIO){
|
14
|
+
TCHAR szVolumePath[_MAX_PATH];
|
15
|
+
HANDLE hCJ;
|
16
|
+
wsprintf(szVolumePath, TEXT("\\\\.\\%c:"), cDriveLetter);
|
17
|
+
|
18
|
+
hCJ = CreateFile(
|
19
|
+
szVolumePath,
|
20
|
+
dwAccess,
|
21
|
+
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
22
|
+
NULL,
|
23
|
+
OPEN_EXISTING,
|
24
|
+
(fAsyncIO ? FILE_FLAG_OVERLAPPED : 0),
|
25
|
+
NULL
|
26
|
+
);
|
27
|
+
|
28
|
+
return(hCJ);
|
29
|
+
}
|
30
|
+
|
31
|
+
/* This function creates a journal on the volume. If a journal already
|
32
|
+
* exists this function will adjust the MaximumSize and AllocationDelta
|
33
|
+
* parameters of the journal.
|
34
|
+
*/
|
35
|
+
BOOL Create(ChangeJournalStruct *ptr, DWORDLONG MaximumSize,
|
36
|
+
DWORDLONG AllocationDelta)
|
37
|
+
{
|
38
|
+
|
39
|
+
DWORD cb;
|
40
|
+
BOOL fOk;
|
41
|
+
CREATE_USN_JOURNAL_DATA cujd;
|
42
|
+
|
43
|
+
cujd.MaximumSize = MaximumSize;
|
44
|
+
cujd.AllocationDelta = AllocationDelta;
|
45
|
+
|
46
|
+
fOk = DeviceIoControl(
|
47
|
+
ptr->hCJ,
|
48
|
+
FSCTL_CREATE_USN_JOURNAL,
|
49
|
+
&cujd,
|
50
|
+
sizeof(cujd),
|
51
|
+
NULL,
|
52
|
+
0,
|
53
|
+
&cb,
|
54
|
+
NULL
|
55
|
+
);
|
56
|
+
|
57
|
+
return(fOk);
|
58
|
+
}
|
59
|
+
|
60
|
+
/* If DeleteFlags specifies USN_DELETE_FLAG_DELETE, the specified journal
|
61
|
+
* will be deleted. If USN_DELETE_FLAG_NOTIFY is specified, the function
|
62
|
+
* waits until the system has finished the delete process.
|
63
|
+
* USN_DELETE_FLAG_NOTIFY can be specified alone to wait for the system
|
64
|
+
* to finish if another application has started deleting a journal.
|
65
|
+
*/
|
66
|
+
BOOL Delete(ChangeJournalStruct *ptr,DWORDLONG UsnJournalID,DWORD DeleteFlags){
|
67
|
+
DWORD cb;
|
68
|
+
DELETE_USN_JOURNAL_DATA dujd;
|
69
|
+
BOOL fOk;
|
70
|
+
|
71
|
+
dujd.UsnJournalID = UsnJournalID;
|
72
|
+
dujd.DeleteFlags = DeleteFlags;
|
73
|
+
|
74
|
+
fOk = DeviceIoControl(
|
75
|
+
ptr->hCJ,
|
76
|
+
FSCTL_DELETE_USN_JOURNAL,
|
77
|
+
&dujd,
|
78
|
+
sizeof(dujd),
|
79
|
+
NULL,
|
80
|
+
0,
|
81
|
+
&cb,
|
82
|
+
NULL
|
83
|
+
);
|
84
|
+
|
85
|
+
return(fOk);
|
86
|
+
}
|
87
|
+
|
88
|
+
/* Return statistics about the journal on the current volume
|
89
|
+
*/
|
90
|
+
BOOL Query(ChangeJournalStruct *ptr, PUSN_JOURNAL_DATA pUsnJournalData) {
|
91
|
+
|
92
|
+
|
93
|
+
DWORD cb;
|
94
|
+
|
95
|
+
BOOL fOk = DeviceIoControl(ptr->hCJ, FSCTL_QUERY_USN_JOURNAL, NULL, 0,
|
96
|
+
pUsnJournalData, sizeof(*pUsnJournalData), &cb, NULL);
|
97
|
+
|
98
|
+
return(fOk);
|
99
|
+
}
|
100
|
+
|
101
|
+
/* This function starts the process of reading data from the journal. The
|
102
|
+
* parameters specify the initial location and filters to use when
|
103
|
+
* reading the journal. The usn parameter may be zero to start reading
|
104
|
+
* at the start of available data. The EnumNext function is called to
|
105
|
+
* actualy get journal records.
|
106
|
+
*/
|
107
|
+
void SeekToUsn(ChangeJournalStruct *ptr,
|
108
|
+
USN usn, DWORD ReasonMask,
|
109
|
+
DWORD ReturnOnlyOnClose, DWORDLONG UsnJournalID) {
|
110
|
+
|
111
|
+
// Store the parameters in rujd. This will determine how we load
|
112
|
+
// buffers with the EnumNext function.
|
113
|
+
ptr->rujd.StartUsn = usn;
|
114
|
+
ptr->rujd.ReasonMask = ReasonMask;
|
115
|
+
ptr->rujd.ReturnOnlyOnClose = ReturnOnlyOnClose;
|
116
|
+
ptr->rujd.Timeout = 0;
|
117
|
+
ptr->rujd.BytesToWaitFor = 0;
|
118
|
+
ptr->rujd.UsnJournalID = UsnJournalID;
|
119
|
+
ptr->cbCJData = 0;
|
120
|
+
ptr->pUsnRecord = NULL;
|
121
|
+
}
|
122
|
+
|
123
|
+
/* This will return the next record in the journal that meets the
|
124
|
+
* requirements specified with the SeekToUsn function. If no more
|
125
|
+
* records are available, the functions returns NULL immediately
|
126
|
+
* (since BytesToWaitFor is zero). This function will also return
|
127
|
+
* NULL if there is an error loading a buffer with the DeviceIoControl
|
128
|
+
* function. Use the GetLastError function to determine the cause of the
|
129
|
+
* error.
|
130
|
+
*
|
131
|
+
* If the EnumNext function returns NULL, GetLastError() may return one
|
132
|
+
* of the following.
|
133
|
+
*
|
134
|
+
* S_OK - There was no error, but there are no more available journal
|
135
|
+
* records. Use the NotifyMoreData function to wait for more data to
|
136
|
+
* become available.
|
137
|
+
*
|
138
|
+
* ERROR_JOURNAL_DELETE_IN_PROGRESS - The journal is being deleted. Use
|
139
|
+
* the Delete function with USN_DELETE_FLAG_NOTIFY to wait for this
|
140
|
+
* process to finish. Then, call the Create function and then SeekToUsn
|
141
|
+
* to start reading from the new journal.
|
142
|
+
*
|
143
|
+
* ERROR_JOURNAL_NOT_ACTIVE - The journal has been deleted. Call the Create
|
144
|
+
* function then SeekToUsn to start reading from the new journal.
|
145
|
+
*
|
146
|
+
* ERROR_INVALID_PARAMETER - Possibly caused if the journal's ID has changed.
|
147
|
+
* Flush all cached data. Call the Query function and then SeekToUsn to
|
148
|
+
* start reading from the new journal.
|
149
|
+
*
|
150
|
+
* ERROR_JOURNAL_ENTRY_DELETED - Journal records were purged from the journal
|
151
|
+
* before we got a chance to process them. Cached information should be
|
152
|
+
* flushed.
|
153
|
+
*/
|
154
|
+
PUSN_RECORD EnumNext(ChangeJournalStruct *ptr) {
|
155
|
+
|
156
|
+
// Make sure we have a buffer to use
|
157
|
+
if(ptr->pbCJData == NULL)
|
158
|
+
rb_raise(cChangeJournalError, "make sure we have a buffer to use");
|
159
|
+
|
160
|
+
// If we do not have a record loaded, or enumerating to the next record
|
161
|
+
// will point us past the end of the output buffer returned
|
162
|
+
// by DeviceIoControl, we need to load a new block of data into memory
|
163
|
+
if( (NULL == ptr->pUsnRecord) ||
|
164
|
+
((PBYTE)ptr->pUsnRecord + ptr->pUsnRecord->RecordLength) >=
|
165
|
+
(ptr->pbCJData + ptr->cbCJData)
|
166
|
+
)
|
167
|
+
{
|
168
|
+
BOOL fOk;
|
169
|
+
ptr->pUsnRecord = NULL;
|
170
|
+
|
171
|
+
fOk = DeviceIoControl(ptr->hCJ, FSCTL_READ_USN_JOURNAL,
|
172
|
+
&ptr->rujd, sizeof(ptr->rujd), ptr->pbCJData,
|
173
|
+
HeapSize(GetProcessHeap(), 0, ptr->pbCJData), &ptr->cbCJData, NULL);
|
174
|
+
if(fOk){
|
175
|
+
// It is possible that DeviceIoControl succeeds, but has not
|
176
|
+
// returned any records - this happens when we reach the end of
|
177
|
+
// available journal records. We return NULL to the user if there's
|
178
|
+
// a real error, or if no records are returned.
|
179
|
+
|
180
|
+
// Set the last error to NO_ERROR so the caller can distinguish
|
181
|
+
// between an error, and the case where no records were returned.
|
182
|
+
SetLastError(NO_ERROR);
|
183
|
+
|
184
|
+
// Store the 'next usn' into m_rujd.StartUsn for use the
|
185
|
+
// next time we want to read from the journal
|
186
|
+
ptr->rujd.StartUsn = * (USN *) ptr->pbCJData;
|
187
|
+
|
188
|
+
// If we got more than sizeof(USN) bytes, we must have a record.
|
189
|
+
// Point the current record to the first record in the buffer
|
190
|
+
if (ptr->cbCJData > sizeof(USN))
|
191
|
+
ptr->pUsnRecord = (PUSN_RECORD) &ptr->pbCJData[sizeof(USN)];
|
192
|
+
}
|
193
|
+
} else {
|
194
|
+
// The next record is already available in our stored
|
195
|
+
// buffer - Move pointer to next record
|
196
|
+
ptr->pUsnRecord = (PUSN_RECORD)
|
197
|
+
((PBYTE) ptr->pUsnRecord + ptr->pUsnRecord->RecordLength);
|
198
|
+
}
|
199
|
+
return(ptr->pUsnRecord);
|
200
|
+
}
|
201
|
+
|
202
|
+
|
203
|
+
/* Cleanup the memory and handles we were using.
|
204
|
+
*/
|
205
|
+
void CleanUp(ChangeJournalStruct *ptr) {
|
206
|
+
USN_JOURNAL_DATA ujd;
|
207
|
+
|
208
|
+
Query(ptr,&ujd);
|
209
|
+
Delete(ptr,ujd.UsnJournalID, USN_DELETE_FLAG_DELETE);
|
210
|
+
|
211
|
+
if (ptr->hCJ != INVALID_HANDLE_VALUE)
|
212
|
+
CloseHandle(ptr->hCJ);
|
213
|
+
if (ptr->hCJAsync != INVALID_HANDLE_VALUE)
|
214
|
+
CloseHandle(ptr->hCJAsync);
|
215
|
+
if (ptr->pbCJData != NULL)
|
216
|
+
HeapFree(GetProcessHeap(), 0, ptr->pbCJData);
|
217
|
+
if (ptr->oCJAsync.hEvent != NULL) {
|
218
|
+
|
219
|
+
// Make sure the helper thread knows that we are exiting. We set
|
220
|
+
// m_hwndApp to NULL then signal the overlapped event to make sure the
|
221
|
+
// helper thread wakes up.
|
222
|
+
SetEvent(ptr->oCJAsync.hEvent);
|
223
|
+
CloseHandle(ptr->oCJAsync.hEvent);
|
224
|
+
}
|
225
|
+
}
|
226
|
+
|
227
|
+
/* Call this to initialize the structure. The cDriveLetter parameter
|
228
|
+
* specifies the drive that this instance will access. The cbBuffer
|
229
|
+
* parameter specifies the size of the interal buffer used to read records
|
230
|
+
* from the journal. This should be large enough to hold several records
|
231
|
+
* (for example, 10 kilobytes will allow this class to buffer several
|
232
|
+
* dozen journal records at a time).
|
233
|
+
*/
|
234
|
+
BOOL Init(ChangeJournalStruct *ptr, TCHAR cDriveLetter, DWORD cbBuffer){
|
235
|
+
if(ptr->pbCJData != NULL){
|
236
|
+
rb_raise(cChangeJournalError,
|
237
|
+
"you should not call this function twice for one instance."
|
238
|
+
);
|
239
|
+
}
|
240
|
+
|
241
|
+
ptr->cDriveLetter = cDriveLetter;
|
242
|
+
|
243
|
+
// Allocate internal buffer
|
244
|
+
ptr->pbCJData = (PBYTE) HeapAlloc(GetProcessHeap(), 0, cbBuffer);
|
245
|
+
if(NULL == ptr->pbCJData){
|
246
|
+
CleanUp(ptr);
|
247
|
+
return(FALSE);
|
248
|
+
}
|
249
|
+
|
250
|
+
// Open a handle to the volume
|
251
|
+
ptr->hCJ = Open(cDriveLetter, GENERIC_WRITE | GENERIC_READ, FALSE);
|
252
|
+
if(INVALID_HANDLE_VALUE == ptr->hCJ){
|
253
|
+
CleanUp(ptr);
|
254
|
+
return(FALSE);
|
255
|
+
}
|
256
|
+
|
257
|
+
// Open a handle to the volume for asynchronous I/O. This is used to wait
|
258
|
+
// for new records after all current records have been processed
|
259
|
+
ptr->hCJAsync = Open(cDriveLetter, GENERIC_WRITE | GENERIC_READ, TRUE);
|
260
|
+
if(INVALID_HANDLE_VALUE == ptr->hCJAsync){
|
261
|
+
CleanUp(ptr);
|
262
|
+
return(FALSE);
|
263
|
+
}
|
264
|
+
|
265
|
+
// Create an event for asynchronous I/O.
|
266
|
+
ptr->oCJAsync.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
267
|
+
if(NULL == ptr->oCJAsync.hEvent){
|
268
|
+
CleanUp(ptr);
|
269
|
+
return(FALSE);
|
270
|
+
}
|
271
|
+
|
272
|
+
return(TRUE);
|
273
|
+
}
|
274
|
+
|
275
|
+
// This function ensures that the journal on the volume is active
|
276
|
+
void InitialzeForMonitoring(ChangeJournalStruct *ptr) {
|
277
|
+
BOOL fOk = TRUE;
|
278
|
+
USN_JOURNAL_DATA ujd;
|
279
|
+
|
280
|
+
// Try to query for current journal information
|
281
|
+
while(fOk && !Query(ptr,&ujd)){
|
282
|
+
switch(GetLastError()){
|
283
|
+
case ERROR_JOURNAL_DELETE_IN_PROGRESS:
|
284
|
+
// The system is deleting a journal. We need to wait for it to finish
|
285
|
+
// before trying to query it again.
|
286
|
+
Delete(ptr, 0, USN_DELETE_FLAG_NOTIFY);
|
287
|
+
break;
|
288
|
+
|
289
|
+
case ERROR_JOURNAL_NOT_ACTIVE:
|
290
|
+
// The journal is not active on the volume. We need to create it and
|
291
|
+
// then query for its information again
|
292
|
+
Create(ptr,0x800000, 0x100000);
|
293
|
+
break;
|
294
|
+
|
295
|
+
default:
|
296
|
+
// Some other error happened while querying the journal information.
|
297
|
+
// There is nothing we can do from here
|
298
|
+
rb_raise(cChangeJournalError, "unable to query journal");
|
299
|
+
fOk = FALSE;
|
300
|
+
break;
|
301
|
+
}
|
302
|
+
}
|
303
|
+
|
304
|
+
// We were not able to query the volume for journal information
|
305
|
+
if(!fOk)
|
306
|
+
return;
|
307
|
+
|
308
|
+
// Start processing records at the start of the journal
|
309
|
+
SeekToUsn(ptr,ujd.FirstUsn, 0xFFFFFFFF, FALSE, ujd.UsnJournalID);
|
310
|
+
|
311
|
+
// Initialize Path DB
|
312
|
+
PopulatePath(ptr);
|
313
|
+
}
|
314
|
+
|
315
|
+
|
316
|
+
/*
|
317
|
+
* :no-doc:
|
318
|
+
*/
|
319
|
+
static VALUE changejournal_allocate(VALUE klass){
|
320
|
+
ChangeJournalStruct* ptr = malloc(sizeof(ChangeJournalStruct));
|
321
|
+
return Data_Wrap_Struct(klass, 0, changejournal_free, ptr);
|
322
|
+
}
|
323
|
+
|
324
|
+
/*
|
325
|
+
* :call-seq:
|
326
|
+
*
|
327
|
+
* ChangeJournal.new(drive)
|
328
|
+
*
|
329
|
+
* Returns a new ChangeJournal object and places a monitor on 'drive'.
|
330
|
+
*/
|
331
|
+
static VALUE changejournal_init(VALUE self, VALUE v_drive)
|
332
|
+
{
|
333
|
+
ChangeJournalStruct* ptr;
|
334
|
+
LPCTSTR lpDriveLetter = StringValuePtr(v_drive);
|
335
|
+
|
336
|
+
Data_Get_Struct(self, ChangeJournalStruct, ptr);
|
337
|
+
|
338
|
+
// Do not allow a block for this class
|
339
|
+
if(rb_block_given_p())
|
340
|
+
rb_raise(cChangeJournalError, "block not permitted for this class");
|
341
|
+
|
342
|
+
// Initialize member variables
|
343
|
+
ptr->hCJ = INVALID_HANDLE_VALUE;
|
344
|
+
ptr->hCJAsync = INVALID_HANDLE_VALUE;
|
345
|
+
ZeroMemory(&(ptr->oCJAsync), sizeof(ptr->oCJAsync));
|
346
|
+
ptr->pbCJData = NULL;
|
347
|
+
ptr->pUsnRecord = NULL;
|
348
|
+
|
349
|
+
// Initialize the ChangeJournal object with the current drive letter and tell
|
350
|
+
// it to allocate a buffer of 10000 bytes to read journal records.
|
351
|
+
if(!Init(ptr, lpDriveLetter[0], 10000))
|
352
|
+
rb_raise(rb_eTypeError, "initialization error");
|
353
|
+
|
354
|
+
InitialzeForMonitoring(ptr);
|
355
|
+
|
356
|
+
return self;
|
357
|
+
}
|
358
|
+
|
359
|
+
/*
|
360
|
+
* :call-seq:
|
361
|
+
*
|
362
|
+
* ChangeJournal#wait(num_seconds=INFINITE)
|
363
|
+
* ChangeJournal#wait(num_seconds=INFINITE){ |s| ... }
|
364
|
+
*
|
365
|
+
* Waits up to 'num_seconds' for a notification to occur, or infinitely if
|
366
|
+
* no value is specified.
|
367
|
+
*
|
368
|
+
* If a block is provided, yields a ChangeJournalStruct that contains two
|
369
|
+
* members - file_name and action.
|
370
|
+
*/
|
371
|
+
static VALUE changejournal_wait(int argc, VALUE* argv, VALUE self){
|
372
|
+
VALUE v_timeout, v_block;
|
373
|
+
ChangeJournalStruct *ptr;
|
374
|
+
DWORD dwTimeout, dwWait;
|
375
|
+
READ_USN_JOURNAL_DATA rujd;
|
376
|
+
BOOL fOk;
|
377
|
+
|
378
|
+
Data_Get_Struct(self, ChangeJournalStruct, ptr);
|
379
|
+
|
380
|
+
rb_scan_args(argc, argv, "01&", &v_timeout, &v_block);
|
381
|
+
|
382
|
+
if(NIL_P(v_timeout)){
|
383
|
+
dwTimeout = INFINITE;
|
384
|
+
}
|
385
|
+
else{
|
386
|
+
dwTimeout = NUM2UINT(v_timeout);
|
387
|
+
dwTimeout *= 1000; // Convert milliseconds to seconds
|
388
|
+
}
|
389
|
+
|
390
|
+
rujd = ptr->rujd;
|
391
|
+
rujd.BytesToWaitFor = 1;
|
392
|
+
|
393
|
+
// Try to read at least one byte from the journal at the specified USN.
|
394
|
+
// When 1 byte is available, the event in m_oCJAsync will be signaled.
|
395
|
+
|
396
|
+
fOk = DeviceIoControl(
|
397
|
+
ptr->hCJAsync,
|
398
|
+
FSCTL_READ_USN_JOURNAL,
|
399
|
+
&rujd,
|
400
|
+
sizeof(rujd),
|
401
|
+
&ptr->UsnAsync,
|
402
|
+
sizeof(ptr->UsnAsync),
|
403
|
+
NULL,
|
404
|
+
&ptr->oCJAsync
|
405
|
+
);
|
406
|
+
|
407
|
+
// Do nothing
|
408
|
+
if(!fOk && (GetLastError() != ERROR_IO_PENDING)) { }
|
409
|
+
|
410
|
+
dwWait = WaitForSingleObject(ptr->oCJAsync.hEvent, dwTimeout);
|
411
|
+
|
412
|
+
switch(dwWait){
|
413
|
+
case WAIT_FAILED:
|
414
|
+
rb_raise(cChangeJournalError, ErrorDescription(GetLastError()));
|
415
|
+
break;
|
416
|
+
case WAIT_OBJECT_0:
|
417
|
+
rb_iv_set(self, "@signaled", Qtrue);
|
418
|
+
if(Qnil != v_block){
|
419
|
+
rb_yield(get_file_action(ptr));
|
420
|
+
}
|
421
|
+
return INT2NUM(1);
|
422
|
+
break;
|
423
|
+
case WAIT_ABANDONED_0:
|
424
|
+
return INT2NUM(-1);
|
425
|
+
break;
|
426
|
+
case WAIT_TIMEOUT:
|
427
|
+
return INT2NUM(0);
|
428
|
+
break;
|
429
|
+
default:
|
430
|
+
rb_raise(cChangeJournalError,
|
431
|
+
"unknown return value from WaitForSingleObject()"
|
432
|
+
);
|
433
|
+
};
|
434
|
+
|
435
|
+
return self;
|
436
|
+
}
|
437
|
+
|
438
|
+
/*
|
439
|
+
* call-seq:
|
440
|
+
*
|
441
|
+
* ChangeJournal#delete
|
442
|
+
*
|
443
|
+
* Deletes the change journal on a volume, or waits for notification of
|
444
|
+
* change journal deletion.
|
445
|
+
*/
|
446
|
+
static VALUE changejournal_delete(VALUE self){
|
447
|
+
ChangeJournalStruct *ptr;
|
448
|
+
USN_JOURNAL_DATA ujd;
|
449
|
+
|
450
|
+
Data_Get_Struct(self, ChangeJournalStruct, ptr);
|
451
|
+
|
452
|
+
Query(ptr, &ujd);
|
453
|
+
Delete(ptr, ujd.UsnJournalID, USN_DELETE_FLAG_DELETE);
|
454
|
+
|
455
|
+
return self;
|
456
|
+
}
|
457
|
+
|
458
|
+
void Init_changejournal()
|
459
|
+
{
|
460
|
+
VALUE mWin32, cChangeJournal;
|
461
|
+
|
462
|
+
// Module and class definitions
|
463
|
+
mWin32 = rb_define_module("Win32");
|
464
|
+
cChangeJournal = rb_define_class_under(mWin32, "ChangeJournal", rb_cObject);
|
465
|
+
cChangeJournalError = rb_define_class_under(mWin32, "ChangeJournalError",
|
466
|
+
rb_eStandardError);
|
467
|
+
|
468
|
+
// ChangeJournal class and instance methods
|
469
|
+
rb_define_alloc_func(cChangeJournal,changejournal_allocate);
|
470
|
+
rb_define_method(cChangeJournal, "initialize", changejournal_init, 1);
|
471
|
+
rb_define_method(cChangeJournal, "wait", changejournal_wait, -1);
|
472
|
+
rb_define_method(cChangeJournal, "delete", changejournal_delete, 0);
|
473
|
+
|
474
|
+
// Struct definitions
|
475
|
+
sChangeJournalStruct = rb_struct_define(
|
476
|
+
"ChangeJournalStruct",
|
477
|
+
"action",
|
478
|
+
"file_name",
|
479
|
+
"path",
|
480
|
+
0
|
481
|
+
);
|
482
|
+
|
483
|
+
// 0.3.2 - The version of the win32-changejournal library
|
484
|
+
rb_define_const(cChangeJournal, "VERSION",
|
485
|
+
rb_str_new2(WIN32_CHANGEJOURNAL_VERSION));
|
486
|
+
}
|
487
|
+
|
Binary file
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#############################################################################
|
2
|
+
# tc_changejournal.rb
|
3
|
+
#
|
4
|
+
# Test suite for the win32-changejournal package. You should run this
|
5
|
+
# via the 'rake test' task.
|
6
|
+
#############################################################################
|
7
|
+
require 'test/unit'
|
8
|
+
require 'win32/changejournal'
|
9
|
+
include Win32
|
10
|
+
|
11
|
+
STDOUT.print "\n\nThis may take a few seconds - be patient\n\n"
|
12
|
+
|
13
|
+
class TC_Win32_ChangeJournal < Test::Unit::TestCase
|
14
|
+
# The thread here is used to force an event to happen for one of the tests
|
15
|
+
def setup
|
16
|
+
@journal = ChangeJournal.new("c:\\")
|
17
|
+
@file = "C:\\delete_me.txt"
|
18
|
+
@thread = Thread.new{
|
19
|
+
sleep 2
|
20
|
+
File.open(@file, 'w+'){ |fh| fh.puts 'Delete me!' }
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_version
|
25
|
+
assert_equal('0.3.2', ChangeJournal::VERSION)
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_changejournal_action
|
29
|
+
@thread.join
|
30
|
+
@journal.wait(2){ |c|
|
31
|
+
assert_kind_of(Array, c)
|
32
|
+
assert_kind_of(Struct::ChangeJournalStruct, c.first)
|
33
|
+
assert_equal(['action', 'file_name', 'path'], c.first.members)
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_delete
|
38
|
+
assert_respond_to(@journal, :delete)
|
39
|
+
assert_nothing_raised{ @journal.delete }
|
40
|
+
end
|
41
|
+
|
42
|
+
# We provide some very short timeouts here - shouldn't slow the tests down
|
43
|
+
def test_wait_basic
|
44
|
+
assert_respond_to(@journal, :wait)
|
45
|
+
assert_nothing_raised{ @journal.wait(0.01) }
|
46
|
+
assert_nothing_raised{ @journal.wait(0.01){ |s| } }
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_wait_expected_errors
|
50
|
+
assert_raise(ArgumentError){ @journal.wait(1,1) }
|
51
|
+
assert_raise(TypeError){ @journal.wait('a') }
|
52
|
+
end
|
53
|
+
|
54
|
+
def teardown
|
55
|
+
@thread.kill rescue nil
|
56
|
+
File.delete(@file) if File.exists?(@file)
|
57
|
+
@journal = nil
|
58
|
+
@flags = nil
|
59
|
+
end
|
60
|
+
end
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: win32-changejournal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Daniel J. Berger
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-05-05 00:00:00 -06:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: A library for monitoring files and directories on NTFS
|
17
|
+
email: djberg96@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
- CHANGES
|
25
|
+
- MANIFEST
|
26
|
+
- ext/win32/changejournal.c
|
27
|
+
files:
|
28
|
+
- lib/win32
|
29
|
+
- lib/win32/changejournal.so
|
30
|
+
- test/tc_changejournal.rb
|
31
|
+
- README
|
32
|
+
- CHANGES
|
33
|
+
- MANIFEST
|
34
|
+
- ext/win32/changejournal.c
|
35
|
+
has_rdoc: true
|
36
|
+
homepage: http://www.rubyforge.org/projects/win32utils
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 1.8.0
|
47
|
+
version:
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
requirements: []
|
55
|
+
|
56
|
+
rubyforge_project: win32utils
|
57
|
+
rubygems_version: 1.1.1
|
58
|
+
signing_key:
|
59
|
+
specification_version: 2
|
60
|
+
summary: A library for monitoring files and directories on NTFS
|
61
|
+
test_files:
|
62
|
+
- test/tc_changejournal.rb
|