win32-changejournal 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
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/test.rb
10
- lib/win32/changejournal.rb
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
- rake install (non-gem)
6
- rake gem-install (gem)
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. On FAT filesystems, you should
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 class was originally based on the CJTest module by Jeffrey
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
- Ruby's
45
+ Artistic 2.0
65
46
 
66
47
  == Copyright
67
- (C) 2003-2008 Daniel J. Berger, All Rights Reserved
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
@@ -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
@@ -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')
@@ -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(cChangeJournalError, "make sure we have a buffer to use");
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(cChangeJournalError,
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(cChangeJournalError, "unable to query journal");
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(drive)
327
+ * Win32::ChangeJournal.new(volume)
328
328
  *
329
- * Returns a new ChangeJournal object and places a monitor on 'drive'.
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(cChangeJournalError, "block not permitted for this class");
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
- // Initialize the ChangeJournal object with the current drive letter and tell
350
- // it to allocate a buffer of 10000 bytes to read journal records.
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){ |s| ... }
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 two
369
- * members - file_name and action.
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(cChangeJournalError, ErrorDescription(GetLastError()));
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(cChangeJournalError,
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
- // Module and class definitions
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
- // ChangeJournal class and instance methods
469
- rb_define_alloc_func(cChangeJournal,changejournal_allocate);
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
- // Struct definitions
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
- // 0.3.2 - The version of the win32-changejournal library
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
- # tc_changejournal.rb
2
+ # test_win32_changejournal.rb
3
3
  #
4
- # Test suite for the win32-changejournal package. You should run this
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
- # The thread here is used to force an event to happen for one of the tests
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
- @file = "C:\\delete_me.txt"
18
- @thread = Thread.new{
23
+ @thread = Thread.new{
19
24
  sleep 2
20
- File.open(@file, 'w+'){ |fh| fh.puts 'Delete me!' }
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.2', ChangeJournal::VERSION)
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 rescue nil
56
- File.delete(@file) if File.exists?(@file)
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.2
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: 2008-05-05 00:00:00 -06:00
13
+ date: 2009-08-18 00:00:00 -06:00
13
14
  default_executable:
14
- dependencies: []
15
-
16
- description: A library for monitoring files and directories on NTFS
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
- - MANIFEST
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.0
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.1.1
72
+ rubygems_version: 1.3.5
58
73
  signing_key:
59
- specification_version: 2
74
+ specification_version: 3
60
75
  summary: A library for monitoring files and directories on NTFS
61
76
  test_files:
62
- - test/tc_changejournal.rb
77
+ - test/test_win32_changejournal.rb
Binary file