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