win32-changejournal 0.3.2
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/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
|