win32-changejournal 0.3.2 → 0.3.3
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 +8 -0
- data/MANIFEST +10 -11
- data/README +9 -28
- data/Rakefile +96 -0
- data/examples/example_changejournal.rb +23 -0
- data/ext/extconf.rb +36 -0
- data/ext/win32/changejournal.c +57 -23
- data/ext/win32/changejournal.h +356 -0
- data/test/{tc_changejournal.rb → test_win32_changejournal.rb} +27 -12
- data/win32-changejournal.gemspec +36 -0
- metadata +31 -16
- data/lib/win32/changejournal.so +0 -0
data/CHANGES
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
== 0.3.3 - 18-Aug-2009
|
2
|
+
* Changed license to Artistic 2.0.
|
3
|
+
* ChangeJournalStruct objects are now frozen. This is read-only data.
|
4
|
+
* ChangeJournalError is now ChangeJournal::Error.
|
5
|
+
* Added test-unit 2.x as a development dependency.
|
6
|
+
* Renamed the test and example files.
|
7
|
+
* Some updates to the Rakefile and gemspec.
|
8
|
+
|
1
9
|
== 0.3.2 - 27-Apr-2008
|
2
10
|
* Fixed RubyForge bug #10555 (bignum too big to convert into long).
|
3
11
|
* Lots of internal reorganization, partially in preparation for the
|
data/MANIFEST
CHANGED
@@ -1,11 +1,10 @@
|
|
1
|
-
CHANGES
|
2
|
-
README
|
3
|
-
MANIFEST
|
4
|
-
Rakefile
|
5
|
-
win32-changejournal.gemspec
|
6
|
-
ext/extconf.rb
|
7
|
-
ext/win32/changejournal.c
|
8
|
-
ext/win32/changejournal.h
|
9
|
-
examples/
|
10
|
-
|
11
|
-
test/tc_changejournal.rb
|
1
|
+
* CHANGES
|
2
|
+
* README
|
3
|
+
* MANIFEST
|
4
|
+
* Rakefile
|
5
|
+
* win32-changejournal.gemspec
|
6
|
+
* ext/extconf.rb
|
7
|
+
* ext/win32/changejournal.c
|
8
|
+
* ext/win32/changejournal.h
|
9
|
+
* examples/example_changejournal.rb
|
10
|
+
* test/test_win32_changejournal.rb
|
data/README
CHANGED
@@ -2,8 +2,10 @@
|
|
2
2
|
A class for monitoring events related to files and directories on NTFS.
|
3
3
|
|
4
4
|
== Installation
|
5
|
-
|
6
|
-
|
5
|
+
=== Gem Installation
|
6
|
+
gem install win32-changejournal
|
7
|
+
=== Local Installation
|
8
|
+
rake install
|
7
9
|
|
8
10
|
== Synopsis
|
9
11
|
require 'win32/changejournal'
|
@@ -11,8 +13,7 @@
|
|
11
13
|
|
12
14
|
# Indefinitely wait for a change in 'C:\' and any of its
|
13
15
|
# subdirectories. Print the file and action affected.
|
14
|
-
|
15
|
-
cj = ChangeJournal.new('C:\')
|
16
|
+
cj = ChangeJournal.new("C:\\")
|
16
17
|
|
17
18
|
cj.wait{ |array|
|
18
19
|
array.each{ |info|
|
@@ -23,34 +24,14 @@
|
|
23
24
|
}
|
24
25
|
|
25
26
|
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
27
|
|
47
28
|
== Notes
|
48
29
|
Based on what the MSDN documentation says, this library requires NTFS, and
|
49
|
-
should be preferred on that filesystem.
|
30
|
+
should be preferred on that filesystem. On FAT filesystems, you should
|
50
31
|
use the win32-changenotify library instead.
|
51
32
|
|
52
33
|
== Acknowledgements
|
53
|
-
This
|
34
|
+
This library was originally based on the CJTest module by Jeffrey
|
54
35
|
Cooperstein & Jeffrey Richter.
|
55
36
|
|
56
37
|
== Future Plans
|
@@ -61,10 +42,10 @@ VERSION
|
|
61
42
|
project page at http://www.rubyforge.net/projects/win32utils
|
62
43
|
|
63
44
|
== License
|
64
|
-
|
45
|
+
Artistic 2.0
|
65
46
|
|
66
47
|
== Copyright
|
67
|
-
(C) 2003-
|
48
|
+
(C) 2003-2009 Daniel J. Berger, All Rights Reserved
|
68
49
|
|
69
50
|
== Warranty
|
70
51
|
This library is provided "as is" and without any express or
|
data/Rakefile
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/clean'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rbconfig'
|
5
|
+
include Config
|
6
|
+
|
7
|
+
desc 'Install the win32-changejournal library'
|
8
|
+
task :install => [:build] do
|
9
|
+
Dir.chdir('ext'){
|
10
|
+
sh 'nmake install'
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
desc 'Clean any build files for win32-changejournal'
|
15
|
+
task :clean do
|
16
|
+
Dir.chdir('ext') do
|
17
|
+
sh 'nmake distclean' if File.exists?('changejournal.obj')
|
18
|
+
rm 'win32/changejournal.so' if File.exists?('win32/changejournal.so')
|
19
|
+
end
|
20
|
+
rm_rf('lib') if File.exists?('lib')
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "Build win32-changejournal (but don't install it)"
|
24
|
+
task :build => [:clean] do
|
25
|
+
Dir.chdir('ext') do
|
26
|
+
ruby 'extconf.rb'
|
27
|
+
sh 'nmake'
|
28
|
+
mv 'changejournal.so', 'win32' # For the test suite
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
desc 'Build gem'
|
33
|
+
task :build_gem do
|
34
|
+
eval(IO.read('win32-changejournal.gemspec'))
|
35
|
+
end
|
36
|
+
|
37
|
+
desc 'Build a binary gem'
|
38
|
+
task :build_binary_gem => [:build] do
|
39
|
+
mkdir_p 'lib/win32'
|
40
|
+
mv 'ext/win32/changejournal.so', 'lib/win32'
|
41
|
+
task :build_binary_gem => [:clean]
|
42
|
+
|
43
|
+
spec = Gem::Specification.new do |gem|
|
44
|
+
gem.name = 'win32-changejournal'
|
45
|
+
gem.version = '0.3.3'
|
46
|
+
gem.authors = ['Daniel J. Berger', 'Park Heesob']
|
47
|
+
gem.license = 'Artistic 2.0'
|
48
|
+
gem.email = 'djberg96@gmail.com'
|
49
|
+
gem.homepage = 'http://www.rubyforge.org/projects/win32utils'
|
50
|
+
gem.platform = Gem::Platform::CURRENT
|
51
|
+
gem.summary = 'A library for monitoring files and directories on NTFS'
|
52
|
+
gem.has_rdoc = true
|
53
|
+
gem.test_file = 'test/test_win32_changejournal.rb'
|
54
|
+
|
55
|
+
gem.files = [
|
56
|
+
Dir['*'],
|
57
|
+
Dir['test/*'],
|
58
|
+
Dir['examples/*'],
|
59
|
+
'ext/extconf.rb',
|
60
|
+
'ext/win32/changejournal.c',
|
61
|
+
'lib/win32/changejournal.so'
|
62
|
+
].flatten.reject{ |f| f.include?('CVS') }
|
63
|
+
|
64
|
+
gem.required_ruby_version = '>= 1.8.2'
|
65
|
+
gem.rubyforge_project = 'win32utils'
|
66
|
+
|
67
|
+
gem.extra_rdoc_files = [
|
68
|
+
'README',
|
69
|
+
'CHANGES',
|
70
|
+
'MANIFEST',
|
71
|
+
'ext/win32/changejournal.c'
|
72
|
+
]
|
73
|
+
|
74
|
+
gem.add_development_dependency('test-unit', '>= 2.0.3')
|
75
|
+
|
76
|
+
gem.description = <<-EOF
|
77
|
+
The win32-changejournal library provides an interface for MS Windows
|
78
|
+
change journals. A change journal is a record of any changes on a given
|
79
|
+
volume maintained by the operating system itself.
|
80
|
+
EOF
|
81
|
+
end
|
82
|
+
|
83
|
+
Gem::Builder.new(spec).build
|
84
|
+
end
|
85
|
+
|
86
|
+
desc 'Run the example program'
|
87
|
+
task :example => [:build] do |t|
|
88
|
+
ruby '-Iext examples/example_changejournal.rb'
|
89
|
+
end
|
90
|
+
|
91
|
+
Rake::TestTask.new(:test) do |t|
|
92
|
+
task :test => [:build]
|
93
|
+
t.libs << 'ext'
|
94
|
+
t.warning = true
|
95
|
+
t.verbose = true
|
96
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
########################################################################
|
2
|
+
# example_changejournal.rb
|
3
|
+
#
|
4
|
+
# A test script for general futzing. Modify as you see fit. You can
|
5
|
+
# run this test script via the 'rake example' task.
|
6
|
+
########################################################################
|
7
|
+
require 'win32/changejournal'
|
8
|
+
|
9
|
+
puts 'VERSION: ' + Win32::ChangeJournal::VERSION
|
10
|
+
|
11
|
+
cj = Win32::ChangeJournal.new('c:\\')
|
12
|
+
|
13
|
+
# Wait up to 5 minutes for a change journals
|
14
|
+
cj.wait(300){ |array|
|
15
|
+
array.each{ |struct|
|
16
|
+
puts 'Something changed'
|
17
|
+
puts 'File: ' + struct.file_name
|
18
|
+
puts 'Action: ' + struct.action
|
19
|
+
puts 'Path: ' + struct.path
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
cj.delete
|
data/ext/extconf.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
require 'Win32API'
|
3
|
+
|
4
|
+
# Ensure that win32-ipc is installed.
|
5
|
+
begin
|
6
|
+
require 'win32/ipc'
|
7
|
+
rescue LoadError
|
8
|
+
STDERR.puts 'The win32-ipc library is a prerequisite for this package.'
|
9
|
+
STDERR.puts 'Please install win32-ipc before continuing.'
|
10
|
+
STDERR.puts 'Exiting...'
|
11
|
+
exit
|
12
|
+
end
|
13
|
+
|
14
|
+
# Set $CPPFLAGS as needed since mkmf doesn't do this for us.
|
15
|
+
GetVersionEx = Win32API.new('kernel32','GetVersionEx','P','I')
|
16
|
+
swCSDVersion = "\0" * 128
|
17
|
+
OSVERSIONINFO = [148,0,0,0,0,swCSDVersion].pack("LLLLLa128")
|
18
|
+
GetVersionEx.call(OSVERSIONINFO)
|
19
|
+
info = OSVERSIONINFO.unpack("LLLLLa128")
|
20
|
+
|
21
|
+
major, minor = info[1,2]
|
22
|
+
platform = info[4]
|
23
|
+
macro = '_WIN32_WINNT='
|
24
|
+
|
25
|
+
case minor
|
26
|
+
when 2 # 2003
|
27
|
+
macro += '0x0502'
|
28
|
+
when 1 # XP
|
29
|
+
macro += '0x0501'
|
30
|
+
else # 2000
|
31
|
+
macro += '0x0500'
|
32
|
+
end
|
33
|
+
|
34
|
+
$CPPFLAGS += " -D#{macro}"
|
35
|
+
|
36
|
+
create_makefile('win32/changejournal', 'win32')
|
data/ext/win32/changejournal.c
CHANGED
@@ -155,7 +155,7 @@ PUSN_RECORD EnumNext(ChangeJournalStruct *ptr) {
|
|
155
155
|
|
156
156
|
// Make sure we have a buffer to use
|
157
157
|
if(ptr->pbCJData == NULL)
|
158
|
-
rb_raise(
|
158
|
+
rb_raise(cCJError, "make sure we have a buffer to use");
|
159
159
|
|
160
160
|
// If we do not have a record loaded, or enumerating to the next record
|
161
161
|
// will point us past the end of the output buffer returned
|
@@ -233,7 +233,7 @@ void CleanUp(ChangeJournalStruct *ptr) {
|
|
233
233
|
*/
|
234
234
|
BOOL Init(ChangeJournalStruct *ptr, TCHAR cDriveLetter, DWORD cbBuffer){
|
235
235
|
if(ptr->pbCJData != NULL){
|
236
|
-
rb_raise(
|
236
|
+
rb_raise(cCJError,
|
237
237
|
"you should not call this function twice for one instance."
|
238
238
|
);
|
239
239
|
}
|
@@ -295,7 +295,7 @@ void InitialzeForMonitoring(ChangeJournalStruct *ptr) {
|
|
295
295
|
default:
|
296
296
|
// Some other error happened while querying the journal information.
|
297
297
|
// There is nothing we can do from here
|
298
|
-
rb_raise(
|
298
|
+
rb_raise(cCJError, "unable to query journal");
|
299
299
|
fOk = FALSE;
|
300
300
|
break;
|
301
301
|
}
|
@@ -324,9 +324,14 @@ static VALUE changejournal_allocate(VALUE klass){
|
|
324
324
|
/*
|
325
325
|
* :call-seq:
|
326
326
|
*
|
327
|
-
* ChangeJournal.new(
|
327
|
+
* Win32::ChangeJournal.new(volume)
|
328
328
|
*
|
329
|
-
* Returns a new ChangeJournal object and places a monitor on
|
329
|
+
* Returns a new ChangeJournal object and places a monitor on +volume+.
|
330
|
+
*
|
331
|
+
* Example:
|
332
|
+
*
|
333
|
+
* # Put a change journal monitor on the C: drive
|
334
|
+
* cj = Win32::Change::Journal.new("C:\\")
|
330
335
|
*/
|
331
336
|
static VALUE changejournal_init(VALUE self, VALUE v_drive)
|
332
337
|
{
|
@@ -337,7 +342,7 @@ static VALUE changejournal_init(VALUE self, VALUE v_drive)
|
|
337
342
|
|
338
343
|
// Do not allow a block for this class
|
339
344
|
if(rb_block_given_p())
|
340
|
-
rb_raise(
|
345
|
+
rb_raise(cCJError, "block not permitted for this class");
|
341
346
|
|
342
347
|
// Initialize member variables
|
343
348
|
ptr->hCJ = INVALID_HANDLE_VALUE;
|
@@ -346,8 +351,9 @@ static VALUE changejournal_init(VALUE self, VALUE v_drive)
|
|
346
351
|
ptr->pbCJData = NULL;
|
347
352
|
ptr->pUsnRecord = NULL;
|
348
353
|
|
349
|
-
|
350
|
-
|
354
|
+
/* Initialize the ChangeJournal object with the current drive letter and tell
|
355
|
+
* it to allocate a buffer of 10000 bytes to read journal records.
|
356
|
+
*/
|
351
357
|
if(!Init(ptr, lpDriveLetter[0], 10000))
|
352
358
|
rb_raise(rb_eTypeError, "initialization error");
|
353
359
|
|
@@ -359,14 +365,28 @@ static VALUE changejournal_init(VALUE self, VALUE v_drive)
|
|
359
365
|
/*
|
360
366
|
* :call-seq:
|
361
367
|
*
|
362
|
-
* ChangeJournal#wait(num_seconds=INFINITE)
|
363
|
-
* ChangeJournal#wait(num_seconds=INFINITE){ |
|
368
|
+
* Win32::ChangeJournal#wait(num_seconds=INFINITE)
|
369
|
+
* Win32::ChangeJournal#wait(num_seconds=INFINITE){ |struct| ... }
|
364
370
|
*
|
365
371
|
* Waits up to 'num_seconds' for a notification to occur, or infinitely if
|
366
372
|
* no value is specified.
|
367
373
|
*
|
368
|
-
* If a block is provided, yields a ChangeJournalStruct that contains
|
369
|
-
* members - file_name and
|
374
|
+
* If a block is provided, yields a frozen ChangeJournalStruct that contains
|
375
|
+
* three members - file_name, action, and path.
|
376
|
+
*
|
377
|
+
* Example:
|
378
|
+
*
|
379
|
+
* require 'win32/changejournal'
|
380
|
+
* cj = Win32::ChangeJournal.new("C:\\")
|
381
|
+
*
|
382
|
+
* # Wait for something to happen for 5 seconds, but ignore the details
|
383
|
+
* cj.wait(5)
|
384
|
+
*
|
385
|
+
* # Wait for 5 seconds, print the details if any, then bail out
|
386
|
+
* cj.wait(5){ |struct| p struct }
|
387
|
+
*
|
388
|
+
* # Wait indefinitely, and print the details when something happens
|
389
|
+
* cj.wait{ |struct| p struct }
|
370
390
|
*/
|
371
391
|
static VALUE changejournal_wait(int argc, VALUE* argv, VALUE self){
|
372
392
|
VALUE v_timeout, v_block;
|
@@ -411,7 +431,7 @@ static VALUE changejournal_wait(int argc, VALUE* argv, VALUE self){
|
|
411
431
|
|
412
432
|
switch(dwWait){
|
413
433
|
case WAIT_FAILED:
|
414
|
-
rb_raise(
|
434
|
+
rb_raise(cCJError, ErrorDescription(GetLastError()));
|
415
435
|
break;
|
416
436
|
case WAIT_OBJECT_0:
|
417
437
|
rb_iv_set(self, "@signaled", Qtrue);
|
@@ -427,7 +447,7 @@ static VALUE changejournal_wait(int argc, VALUE* argv, VALUE self){
|
|
427
447
|
return INT2NUM(0);
|
428
448
|
break;
|
429
449
|
default:
|
430
|
-
rb_raise(
|
450
|
+
rb_raise(cCJError,
|
431
451
|
"unknown return value from WaitForSingleObject()"
|
432
452
|
);
|
433
453
|
};
|
@@ -438,10 +458,17 @@ static VALUE changejournal_wait(int argc, VALUE* argv, VALUE self){
|
|
438
458
|
/*
|
439
459
|
* call-seq:
|
440
460
|
*
|
441
|
-
* ChangeJournal#delete
|
461
|
+
* Win32::ChangeJournal#delete
|
442
462
|
*
|
443
463
|
* Deletes the change journal on a volume, or waits for notification of
|
444
464
|
* change journal deletion.
|
465
|
+
*
|
466
|
+
* Example:
|
467
|
+
*
|
468
|
+
* require 'win32/changejournal'
|
469
|
+
* cj = Win32::ChangeJournal.new("C:\\")
|
470
|
+
* cj.wait(5)
|
471
|
+
* cj.delete
|
445
472
|
*/
|
446
473
|
static VALUE changejournal_delete(VALUE self){
|
447
474
|
ChangeJournalStruct *ptr;
|
@@ -459,19 +486,27 @@ void Init_changejournal()
|
|
459
486
|
{
|
460
487
|
VALUE mWin32, cChangeJournal;
|
461
488
|
|
462
|
-
|
489
|
+
/* The Win32 module serves as a namespace only. */
|
463
490
|
mWin32 = rb_define_module("Win32");
|
491
|
+
|
492
|
+
/* The Win32::ChangeJournal class encapsulates functions related to a
|
493
|
+
* Windows change journal.
|
494
|
+
*/
|
464
495
|
cChangeJournal = rb_define_class_under(mWin32, "ChangeJournal", rb_cObject);
|
465
|
-
cChangeJournalError = rb_define_class_under(mWin32, "ChangeJournalError",
|
466
|
-
rb_eStandardError);
|
467
496
|
|
468
|
-
|
469
|
-
|
497
|
+
/* The ChangeJournal::Error class is typically raised if any of the
|
498
|
+
* Win32::ChangeJournal methods fail.
|
499
|
+
*/
|
500
|
+
cCJError = rb_define_class_under(cChangeJournal, "Error", rb_eStandardError);
|
501
|
+
|
502
|
+
/* ChangeJournal class and instance methods */
|
503
|
+
rb_define_alloc_func(cChangeJournal, changejournal_allocate);
|
504
|
+
|
470
505
|
rb_define_method(cChangeJournal, "initialize", changejournal_init, 1);
|
471
506
|
rb_define_method(cChangeJournal, "wait", changejournal_wait, -1);
|
472
507
|
rb_define_method(cChangeJournal, "delete", changejournal_delete, 0);
|
473
508
|
|
474
|
-
|
509
|
+
/* Struct definitions */
|
475
510
|
sChangeJournalStruct = rb_struct_define(
|
476
511
|
"ChangeJournalStruct",
|
477
512
|
"action",
|
@@ -480,8 +515,7 @@ void Init_changejournal()
|
|
480
515
|
0
|
481
516
|
);
|
482
517
|
|
483
|
-
|
518
|
+
/* 0.3.3: The version of the win32-changejournal library */
|
484
519
|
rb_define_const(cChangeJournal, "VERSION",
|
485
520
|
rb_str_new2(WIN32_CHANGEJOURNAL_VERSION));
|
486
521
|
}
|
487
|
-
|
@@ -0,0 +1,356 @@
|
|
1
|
+
#define WIN32_CHANGEJOURNAL_VERSION "0.3.3"
|
2
|
+
#define MAX_STRING 1024
|
3
|
+
|
4
|
+
static VALUE cCJError;
|
5
|
+
static VALUE sChangeJournalStruct;
|
6
|
+
static char error[MAX_STRING];
|
7
|
+
static VALUE hPathDB;
|
8
|
+
|
9
|
+
// Prototype
|
10
|
+
BOOL Query(ChangeJournalStruct, PUSN_JOURNAL_DATA);
|
11
|
+
void add_reason(char*, const char*);
|
12
|
+
|
13
|
+
struct changejournalstruct {
|
14
|
+
TCHAR cDriveLetter; // drive letter of volume
|
15
|
+
HANDLE hCJ; // handle to volume
|
16
|
+
|
17
|
+
// Members used to enumerate journal records
|
18
|
+
READ_USN_JOURNAL_DATA rujd; // parameters for reading records
|
19
|
+
PBYTE pbCJData; // buffer of records
|
20
|
+
DWORD cbCJData; // valid bytes in buffer
|
21
|
+
PUSN_RECORD pUsnRecord; // pointer to current record
|
22
|
+
|
23
|
+
// Members used to notify application of new data
|
24
|
+
HANDLE hCJAsync; // Async handle to volume
|
25
|
+
OVERLAPPED oCJAsync; // overlapped structure
|
26
|
+
USN UsnAsync; // output buffer for overlapped I/O
|
27
|
+
DWORD dwDelay; // delay before sending notification
|
28
|
+
};
|
29
|
+
|
30
|
+
typedef struct changejournalstruct ChangeJournalStruct;
|
31
|
+
|
32
|
+
void CleanUp(ChangeJournalStruct *ptr);
|
33
|
+
|
34
|
+
static void changejournal_free(ChangeJournalStruct *p)
|
35
|
+
{
|
36
|
+
CleanUp(p);
|
37
|
+
free(p);
|
38
|
+
}
|
39
|
+
|
40
|
+
// Helper function for the get_file_action function.
|
41
|
+
void add_reason(char* str, const char* reason){
|
42
|
+
if(strlen(str) > 0)
|
43
|
+
strcat(str, ", ");
|
44
|
+
|
45
|
+
strcat(str, reason);
|
46
|
+
}
|
47
|
+
|
48
|
+
PUSN_RECORD EnumNext(ChangeJournalStruct *ptr);
|
49
|
+
|
50
|
+
// Enumerate the MFT for all entries. Store the file reference numbers of
|
51
|
+
// any directories in the database.
|
52
|
+
void PopulatePath(ChangeJournalStruct *ptr) {
|
53
|
+
USN_JOURNAL_DATA ujd;
|
54
|
+
BY_HANDLE_FILE_INFORMATION fi;
|
55
|
+
TCHAR szRoot[_MAX_PATH];
|
56
|
+
HANDLE hDir;
|
57
|
+
MFT_ENUM_DATA med;
|
58
|
+
BYTE pData[sizeof(DWORDLONG) + 0x10000]; // Process MFT in 64k chunks
|
59
|
+
DWORDLONG fnLast = 0;
|
60
|
+
DWORDLONG IndexRoot, FRN, PFRN;
|
61
|
+
DWORD cb;
|
62
|
+
LPWSTR pszFileName;
|
63
|
+
WCHAR szFile[MAX_PATH];
|
64
|
+
int cFileName;
|
65
|
+
VALUE v_path_db;
|
66
|
+
|
67
|
+
v_path_db = rb_hash_new();
|
68
|
+
Query(ptr, &ujd);
|
69
|
+
|
70
|
+
// Get the FRN of the root directory
|
71
|
+
wsprintf(szRoot, TEXT("%c:\\"), ptr->cDriveLetter);
|
72
|
+
|
73
|
+
hDir = CreateFile(
|
74
|
+
szRoot,
|
75
|
+
0,
|
76
|
+
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
77
|
+
NULL,
|
78
|
+
OPEN_EXISTING,
|
79
|
+
FILE_FLAG_BACKUP_SEMANTICS,
|
80
|
+
NULL
|
81
|
+
);
|
82
|
+
|
83
|
+
GetFileInformationByHandle(hDir, &fi);
|
84
|
+
CloseHandle(hDir);
|
85
|
+
|
86
|
+
IndexRoot = (((DWORDLONG) fi.nFileIndexHigh) << 32) | fi.nFileIndexLow;
|
87
|
+
|
88
|
+
wsprintf(szRoot, TEXT("%c:"), ptr->cDriveLetter);
|
89
|
+
|
90
|
+
FRN = IndexRoot;
|
91
|
+
PFRN = 0;
|
92
|
+
|
93
|
+
rb_hash_aset(v_path_db,ULL2NUM(FRN),
|
94
|
+
rb_ary_new3(2, rb_str_new(szRoot,2), ULL2NUM(PFRN)));
|
95
|
+
|
96
|
+
med.StartFileReferenceNumber = 0;
|
97
|
+
med.LowUsn = 0;
|
98
|
+
med.HighUsn = ujd.NextUsn;
|
99
|
+
|
100
|
+
while (DeviceIoControl(ptr->hCJ, FSCTL_ENUM_USN_DATA, &med, sizeof(med),
|
101
|
+
pData, sizeof(pData), &cb, NULL) != FALSE) {
|
102
|
+
PUSN_RECORD pRecord = (PUSN_RECORD) &pData[sizeof(USN)];
|
103
|
+
|
104
|
+
while((PBYTE) pRecord < (pData + cb)){
|
105
|
+
if(0 != (pRecord->FileAttributes & FILE_ATTRIBUTE_DIRECTORY)){
|
106
|
+
pszFileName = (LPWSTR)((PBYTE) pRecord + pRecord->FileNameOffset);
|
107
|
+
cFileName = pRecord->FileNameLength / sizeof(WCHAR);
|
108
|
+
|
109
|
+
wcsncpy(szFile, pszFileName, cFileName);
|
110
|
+
szFile[cFileName] = 0;
|
111
|
+
|
112
|
+
FRN = pRecord->FileReferenceNumber;
|
113
|
+
PFRN = pRecord->ParentFileReferenceNumber;
|
114
|
+
|
115
|
+
WideCharToMultiByte(
|
116
|
+
CP_ACP,
|
117
|
+
0,
|
118
|
+
szFile,
|
119
|
+
-1,
|
120
|
+
szRoot,
|
121
|
+
MAX_PATH,
|
122
|
+
NULL,
|
123
|
+
NULL
|
124
|
+
);
|
125
|
+
|
126
|
+
rb_hash_aset(
|
127
|
+
v_path_db,
|
128
|
+
ULL2NUM(FRN),
|
129
|
+
rb_ary_new3(2, rb_str_new2((char*)szRoot), ULL2NUM(PFRN))
|
130
|
+
);
|
131
|
+
|
132
|
+
}
|
133
|
+
pRecord = (PUSN_RECORD) ((PBYTE) pRecord + pRecord->RecordLength);
|
134
|
+
}
|
135
|
+
|
136
|
+
med.StartFileReferenceNumber = * (DWORDLONG *) pData;
|
137
|
+
}
|
138
|
+
|
139
|
+
hPathDB = v_path_db;
|
140
|
+
}
|
141
|
+
|
142
|
+
// Returns both the file action and name in a Ruby struct
|
143
|
+
static VALUE get_file_action(ChangeJournalStruct *ptr){
|
144
|
+
VALUE v_struct, v_action, v_array, v_arr, v_path, v_path_array;
|
145
|
+
WCHAR szFile[MAX_PATH];
|
146
|
+
char file_name[MAX_PATH];
|
147
|
+
char path[MAX_PATH];
|
148
|
+
char szReason[MAX_STRING];
|
149
|
+
LPWSTR pszFileName;
|
150
|
+
int cFileName;
|
151
|
+
DWORDLONG FRN;
|
152
|
+
DWORDLONG PFRN;
|
153
|
+
VALUE v_path_db = hPathDB;
|
154
|
+
|
155
|
+
v_array = rb_ary_new();
|
156
|
+
|
157
|
+
while(EnumNext(ptr)) {
|
158
|
+
pszFileName =
|
159
|
+
(LPWSTR)((PBYTE) ptr->pUsnRecord + ptr->pUsnRecord->FileNameOffset);
|
160
|
+
|
161
|
+
cFileName = ptr->pUsnRecord->FileNameLength / sizeof(WCHAR);
|
162
|
+
wcsncpy(szFile, pszFileName, cFileName);
|
163
|
+
szFile[cFileName] = 0;
|
164
|
+
|
165
|
+
// If this is a close record for a directory, we may need to adjust
|
166
|
+
// our directory database
|
167
|
+
|
168
|
+
if(0 != (ptr->pUsnRecord->FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
169
|
+
&& 0 != (ptr->pUsnRecord->Reason & USN_REASON_CLOSE))
|
170
|
+
{
|
171
|
+
|
172
|
+
FRN = ptr->pUsnRecord->FileReferenceNumber;
|
173
|
+
PFRN = ptr->pUsnRecord->ParentFileReferenceNumber;
|
174
|
+
|
175
|
+
WideCharToMultiByte(
|
176
|
+
CP_ACP,
|
177
|
+
0,
|
178
|
+
szFile,
|
179
|
+
-1,
|
180
|
+
path,
|
181
|
+
MAX_PATH,
|
182
|
+
NULL,
|
183
|
+
NULL
|
184
|
+
);
|
185
|
+
|
186
|
+
// Process newly created directories
|
187
|
+
if(0 != (ptr->pUsnRecord->Reason & USN_REASON_FILE_CREATE)){
|
188
|
+
rb_hash_aset(v_path_db,ULL2NUM(FRN),
|
189
|
+
rb_ary_new3(2, rb_str_new2(path), ULL2NUM(PFRN)));
|
190
|
+
}
|
191
|
+
|
192
|
+
// Process renamed directories
|
193
|
+
if(0 != (ptr->pUsnRecord->Reason & USN_REASON_RENAME_NEW_NAME)) {
|
194
|
+
rb_hash_aset(v_path_db,ULL2NUM(FRN),
|
195
|
+
rb_ary_new3(2, rb_str_new2(path), ULL2NUM(PFRN)));
|
196
|
+
}
|
197
|
+
|
198
|
+
// Process deleted directories
|
199
|
+
if(0 != (ptr->pUsnRecord->Reason & USN_REASON_FILE_DELETE)){
|
200
|
+
rb_hash_delete(v_path_db,ULL2NUM(FRN));
|
201
|
+
}
|
202
|
+
}
|
203
|
+
|
204
|
+
szReason[0] = 0;
|
205
|
+
|
206
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_DATA_OVERWRITE)
|
207
|
+
add_reason(szReason, "data_overwrite");
|
208
|
+
|
209
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_DATA_EXTEND)
|
210
|
+
add_reason(szReason, "data_extend");
|
211
|
+
|
212
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_DATA_TRUNCATION)
|
213
|
+
add_reason(szReason, "data_truncation");
|
214
|
+
|
215
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_NAMED_DATA_OVERWRITE)
|
216
|
+
add_reason(szReason, "named_data_overwrite");
|
217
|
+
|
218
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_NAMED_DATA_EXTEND)
|
219
|
+
add_reason(szReason, "named_data_extend");
|
220
|
+
|
221
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_NAMED_DATA_TRUNCATION)
|
222
|
+
add_reason(szReason, "named_data_truncation");
|
223
|
+
|
224
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_FILE_CREATE)
|
225
|
+
add_reason(szReason, "file_create");
|
226
|
+
|
227
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_FILE_DELETE)
|
228
|
+
add_reason(szReason, "file_delete");
|
229
|
+
|
230
|
+
#ifdef USN_REASON_PROPERTY_CHANGE
|
231
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_PROPERTY_CHANGE)
|
232
|
+
add_reason(szReason, "property_change");
|
233
|
+
#endif
|
234
|
+
|
235
|
+
#ifdef USN_REASON_EA_CHANGE
|
236
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_EA_CHANGE)
|
237
|
+
add_reason(szReason, "property_change");
|
238
|
+
#endif
|
239
|
+
|
240
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_SECURITY_CHANGE)
|
241
|
+
add_reason(szReason, "security_change");
|
242
|
+
|
243
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_RENAME_OLD_NAME)
|
244
|
+
add_reason(szReason, "rename_old_name");
|
245
|
+
|
246
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_RENAME_NEW_NAME)
|
247
|
+
add_reason(szReason, "rename_new_name");
|
248
|
+
|
249
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_INDEXABLE_CHANGE)
|
250
|
+
add_reason(szReason, "indexable_change");
|
251
|
+
|
252
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_BASIC_INFO_CHANGE)
|
253
|
+
add_reason(szReason, "basic_info_change");
|
254
|
+
|
255
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_HARD_LINK_CHANGE)
|
256
|
+
add_reason(szReason, "hard_link_change");
|
257
|
+
|
258
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_COMPRESSION_CHANGE)
|
259
|
+
add_reason(szReason, "compression_change");
|
260
|
+
|
261
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_ENCRYPTION_CHANGE)
|
262
|
+
add_reason(szReason, "encryption_change");
|
263
|
+
|
264
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_OBJECT_ID_CHANGE)
|
265
|
+
add_reason(szReason, "object_id_change");
|
266
|
+
|
267
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_REPARSE_POINT_CHANGE)
|
268
|
+
add_reason(szReason, "reparse_point_change");
|
269
|
+
|
270
|
+
#ifdef USN_REASON_MOUNT_TABLE_CHANGE
|
271
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_MOUNT_TABLE_CHANGE)
|
272
|
+
add_reason(szReason, "stream_change");
|
273
|
+
|
274
|
+
#endif
|
275
|
+
#ifdef USN_REASON_STREAM_CHANGE
|
276
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_STREAM_CHANGE)
|
277
|
+
add_reason(szReason, "stream_change");
|
278
|
+
#endif
|
279
|
+
|
280
|
+
if(ptr->pUsnRecord->Reason & USN_REASON_CLOSE)
|
281
|
+
add_reason(szReason, "close");
|
282
|
+
|
283
|
+
v_action = rb_str_new2(szReason);
|
284
|
+
|
285
|
+
*path = '\0';
|
286
|
+
|
287
|
+
FRN = ptr->pUsnRecord->ParentFileReferenceNumber;
|
288
|
+
v_path_array = rb_ary_new();
|
289
|
+
|
290
|
+
while(FRN != 0) {
|
291
|
+
v_arr = rb_hash_aref(v_path_db,ULL2NUM(FRN));
|
292
|
+
|
293
|
+
if(NIL_P(v_arr))
|
294
|
+
break;
|
295
|
+
|
296
|
+
v_path = RARRAY(v_arr)->ptr[0];
|
297
|
+
rb_ary_unshift(v_path_array, v_path);
|
298
|
+
FRN = NUM2ULL(RARRAY(rb_hash_aref(v_path_db, ULL2NUM(FRN)))->ptr[1]);
|
299
|
+
}
|
300
|
+
|
301
|
+
v_path = rb_ary_join(v_path_array, rb_str_new("\\", 1));
|
302
|
+
strcpy(path, StringValuePtr(v_path));
|
303
|
+
|
304
|
+
WideCharToMultiByte(
|
305
|
+
CP_ACP,
|
306
|
+
0,
|
307
|
+
szFile,
|
308
|
+
-1,
|
309
|
+
file_name,
|
310
|
+
MAX_PATH,
|
311
|
+
NULL,
|
312
|
+
NULL
|
313
|
+
);
|
314
|
+
|
315
|
+
v_struct = rb_struct_new(
|
316
|
+
sChangeJournalStruct,
|
317
|
+
v_action,
|
318
|
+
rb_str_new2(file_name),
|
319
|
+
rb_str_new2(path)
|
320
|
+
);
|
321
|
+
|
322
|
+
// This is read-only information
|
323
|
+
rb_obj_freeze(v_struct);
|
324
|
+
|
325
|
+
rb_ary_push(v_array, v_struct);
|
326
|
+
}
|
327
|
+
|
328
|
+
return v_array;
|
329
|
+
}
|
330
|
+
|
331
|
+
// Return an error code as a string
|
332
|
+
LPTSTR ErrorDescription(DWORD p_dwError)
|
333
|
+
{
|
334
|
+
HLOCAL hLocal = NULL;
|
335
|
+
static char ErrStr[MAX_STRING];
|
336
|
+
int len;
|
337
|
+
|
338
|
+
if (!(len=FormatMessage(
|
339
|
+
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
340
|
+
FORMAT_MESSAGE_FROM_SYSTEM |
|
341
|
+
FORMAT_MESSAGE_IGNORE_INSERTS,
|
342
|
+
NULL,
|
343
|
+
p_dwError,
|
344
|
+
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
|
345
|
+
(LPTSTR)&hLocal,
|
346
|
+
0,
|
347
|
+
NULL)))
|
348
|
+
{
|
349
|
+
rb_raise(rb_eStandardError, "Unable to format error message");
|
350
|
+
}
|
351
|
+
|
352
|
+
memset(ErrStr, 0, MAX_STRING);
|
353
|
+
strncpy(ErrStr, (LPTSTR)hLocal, len-2); // remove \r\n
|
354
|
+
LocalFree(hLocal);
|
355
|
+
return ErrStr;
|
356
|
+
}
|
@@ -1,28 +1,33 @@
|
|
1
1
|
#############################################################################
|
2
|
-
#
|
2
|
+
# test_win32_changejournal.rb
|
3
3
|
#
|
4
|
-
# Test suite for the win32-changejournal
|
4
|
+
# Test suite for the win32-changejournal library. You should run this
|
5
5
|
# via the 'rake test' task.
|
6
6
|
#############################################################################
|
7
|
+
require 'rubygems'
|
8
|
+
gem 'test-unit'
|
9
|
+
|
7
10
|
require 'test/unit'
|
8
11
|
require 'win32/changejournal'
|
9
12
|
include Win32
|
10
13
|
|
11
|
-
STDOUT.print "\n\nThis may take a few seconds - be patient\n\n"
|
12
|
-
|
13
14
|
class TC_Win32_ChangeJournal < Test::Unit::TestCase
|
14
|
-
|
15
|
+
def self.startup
|
16
|
+
Dir.chdir(File.expand_path(File.dirname(__FILE__)))
|
17
|
+
@@file = 'win32_changejournal_test.txt'
|
18
|
+
end
|
19
|
+
|
15
20
|
def setup
|
21
|
+
# The thread is used to force an event to happen for the tests
|
16
22
|
@journal = ChangeJournal.new("c:\\")
|
17
|
-
@
|
18
|
-
@thread = Thread.new{
|
23
|
+
@thread = Thread.new{
|
19
24
|
sleep 2
|
20
|
-
File.open(
|
25
|
+
File.open(@@file, 'w'){ |fh| fh.puts 'Delete me!' }
|
21
26
|
}
|
22
27
|
end
|
23
28
|
|
24
29
|
def test_version
|
25
|
-
assert_equal('0.3.
|
30
|
+
assert_equal('0.3.3', ChangeJournal::VERSION)
|
26
31
|
end
|
27
32
|
|
28
33
|
def test_changejournal_action
|
@@ -31,6 +36,7 @@ class TC_Win32_ChangeJournal < Test::Unit::TestCase
|
|
31
36
|
assert_kind_of(Array, c)
|
32
37
|
assert_kind_of(Struct::ChangeJournalStruct, c.first)
|
33
38
|
assert_equal(['action', 'file_name', 'path'], c.first.members)
|
39
|
+
assert_true(c.first.frozen?)
|
34
40
|
}
|
35
41
|
end
|
36
42
|
|
@@ -39,7 +45,7 @@ class TC_Win32_ChangeJournal < Test::Unit::TestCase
|
|
39
45
|
assert_nothing_raised{ @journal.delete }
|
40
46
|
end
|
41
47
|
|
42
|
-
# We provide some very short timeouts here - shouldn't slow the tests down
|
48
|
+
# We provide some very short timeouts here - shouldn't slow the tests down
|
43
49
|
def test_wait_basic
|
44
50
|
assert_respond_to(@journal, :wait)
|
45
51
|
assert_nothing_raised{ @journal.wait(0.01) }
|
@@ -50,11 +56,20 @@ class TC_Win32_ChangeJournal < Test::Unit::TestCase
|
|
50
56
|
assert_raise(ArgumentError){ @journal.wait(1,1) }
|
51
57
|
assert_raise(TypeError){ @journal.wait('a') }
|
52
58
|
end
|
59
|
+
|
60
|
+
def test_error_class_defined
|
61
|
+
assert_kind_of(Object, Win32::ChangeJournal::Error)
|
62
|
+
end
|
53
63
|
|
54
64
|
def teardown
|
55
|
-
@thread.kill
|
56
|
-
|
65
|
+
@thread.kill if @thread.alive?
|
66
|
+
|
57
67
|
@journal = nil
|
58
68
|
@flags = nil
|
69
|
+
@thread = nil
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.shutdown
|
73
|
+
File.delete(@@file) if File.exists?(@@file)
|
59
74
|
end
|
60
75
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
spec = Gem::Specification.new do |gem|
|
4
|
+
gem.name = 'win32-changejournal'
|
5
|
+
gem.version = '0.3.3'
|
6
|
+
gem.authors = ['Daniel J. Berger', 'Park Heesob']
|
7
|
+
gem.license = 'Artistic 2.0'
|
8
|
+
gem.email = 'djberg96@gmail.com'
|
9
|
+
gem.homepage = 'http://www.rubyforge.org/projects/win32utils'
|
10
|
+
gem.platform = Gem::Platform::RUBY
|
11
|
+
gem.summary = 'A library for monitoring files and directories on NTFS'
|
12
|
+
gem.has_rdoc = true
|
13
|
+
gem.test_file = 'test/test_win32_changejournal.rb'
|
14
|
+
gem.extensions = ['ext/extconf.rb']
|
15
|
+
gem.files = Dir['**/*'].reject{ |f| f.include?('CVS') }
|
16
|
+
|
17
|
+
gem.required_ruby_version = '>= 1.8.2'
|
18
|
+
gem.rubyforge_project = 'win32utils'
|
19
|
+
|
20
|
+
gem.extra_rdoc_files = [
|
21
|
+
'README',
|
22
|
+
'CHANGES',
|
23
|
+
'MANIFEST',
|
24
|
+
'ext/win32/changejournal.c'
|
25
|
+
]
|
26
|
+
|
27
|
+
gem.add_development_dependency('test-unit', '>= 2.0.3')
|
28
|
+
|
29
|
+
gem.description = <<-EOF
|
30
|
+
The win32-changejournal library provides an interface for MS Windows
|
31
|
+
change journals. A change journal is a record of any changes on a given
|
32
|
+
volume maintained by the operating system itself.
|
33
|
+
EOF
|
34
|
+
end
|
35
|
+
|
36
|
+
Gem::Builder.new(spec).build
|
metadata
CHANGED
@@ -1,39 +1,54 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: win32-changejournal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel J. Berger
|
8
|
+
- Park Heesob
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
12
|
|
12
|
-
date:
|
13
|
+
date: 2009-08-18 00:00:00 -06:00
|
13
14
|
default_executable:
|
14
|
-
dependencies:
|
15
|
-
|
16
|
-
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: test-unit
|
18
|
+
type: :development
|
19
|
+
version_requirement:
|
20
|
+
version_requirements: !ruby/object:Gem::Requirement
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 2.0.3
|
25
|
+
version:
|
26
|
+
description: " The win32-changejournal library provides an interface for MS Windows\n change journals. A change journal is a record of any changes on a given\n volume maintained by the operating system itself.\n"
|
17
27
|
email: djberg96@gmail.com
|
18
28
|
executables: []
|
19
29
|
|
20
|
-
extensions:
|
21
|
-
|
30
|
+
extensions:
|
31
|
+
- ext/extconf.rb
|
22
32
|
extra_rdoc_files:
|
23
33
|
- README
|
24
34
|
- CHANGES
|
25
35
|
- MANIFEST
|
26
36
|
- ext/win32/changejournal.c
|
27
37
|
files:
|
28
|
-
- lib/win32
|
29
|
-
- lib/win32/changejournal.so
|
30
|
-
- test/tc_changejournal.rb
|
31
|
-
- README
|
32
38
|
- CHANGES
|
33
|
-
-
|
39
|
+
- examples/example_changejournal.rb
|
40
|
+
- ext/extconf.rb
|
34
41
|
- ext/win32/changejournal.c
|
42
|
+
- ext/win32/changejournal.h
|
43
|
+
- MANIFEST
|
44
|
+
- Rakefile
|
45
|
+
- README
|
46
|
+
- test/test_win32_changejournal.rb
|
47
|
+
- win32-changejournal.gemspec
|
35
48
|
has_rdoc: true
|
36
49
|
homepage: http://www.rubyforge.org/projects/win32utils
|
50
|
+
licenses:
|
51
|
+
- Artistic 2.0
|
37
52
|
post_install_message:
|
38
53
|
rdoc_options: []
|
39
54
|
|
@@ -43,7 +58,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
43
58
|
requirements:
|
44
59
|
- - ">="
|
45
60
|
- !ruby/object:Gem::Version
|
46
|
-
version: 1.8.
|
61
|
+
version: 1.8.2
|
47
62
|
version:
|
48
63
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
64
|
requirements:
|
@@ -54,9 +69,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
54
69
|
requirements: []
|
55
70
|
|
56
71
|
rubyforge_project: win32utils
|
57
|
-
rubygems_version: 1.
|
72
|
+
rubygems_version: 1.3.5
|
58
73
|
signing_key:
|
59
|
-
specification_version:
|
74
|
+
specification_version: 3
|
60
75
|
summary: A library for monitoring files and directories on NTFS
|
61
76
|
test_files:
|
62
|
-
- test/
|
77
|
+
- test/test_win32_changejournal.rb
|
data/lib/win32/changejournal.so
DELETED
Binary file
|