filewatch 0.2.4 → 0.2.5

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.
@@ -10,9 +10,18 @@ class FileWatch::Inotify::Event < FFI::Struct
10
10
  :len, :uint32
11
11
  # last piece is the name, but don't hold it in the struct
12
12
  #:name, :string,
13
+
14
+ # helper accessors
15
+ def wd; return self[:wd]; end
16
+ def mask; return self[:mask]; end
17
+ def cookie; return self[:cookie]; end
18
+ def len; return self[:len]; end
13
19
 
14
20
  attr_accessor :name
15
21
 
22
+ # This attribute is only set for file renames
23
+ attr_accessor :old_name
24
+
16
25
  # Enum of :directory or :file
17
26
  attr_accessor :type
18
27
 
@@ -75,8 +84,10 @@ class FileWatch::Inotify::Event < FFI::Struct
75
84
  end
76
85
 
77
86
  def actions
87
+ # TODO(sissel): Skip these?
78
88
  ::FileWatch::Inotify::FD::WATCH_BITS.reject do |key, bitmask|
79
- self[:mask] & bitmask == 0
89
+ #p key => [self.mask, bitmask] if self.mask & bitmask != 0
90
+ self.mask & bitmask == 0
80
91
  end.keys
81
92
  end
82
93
 
@@ -44,18 +44,20 @@ class FileWatch::Inotify::FD
44
44
  :delete => 1 << 9,
45
45
  :delete_self => 1 << 10,
46
46
  :move_self => 1 << 11,
47
+ }
47
48
 
48
49
  # Shortcuts
49
- :close => (1 << 3) | (1 << 4),
50
- :move => (1 << 6) | (1 << 7) | (1 << 11),
51
- :delete => (1 << 9) | (1 << 10),
52
- }
50
+ WATCH_BITS[:close] = WATCH_BITS[:close_write] | WATCH_BITS[:close_nowrite]
51
+ WATCH_BITS[:move] = WATCH_BITS[:moved_from] \
52
+ | WATCH_BITS[:moved_to] | WATCH_BITS[:move_self]
53
+ WATCH_BITS[:delete] = WATCH_BITS[:delete] | WATCH_BITS[:delete_self]
53
54
 
54
55
  attr_reader :fd
55
56
 
56
57
  public
57
58
  def self.can_watch?(filestat)
58
59
  # TODO(sissel): implement.
60
+ return true
59
61
  end # def self.can_watch?
60
62
 
61
63
  # Create a new FileWatch::Inotify::FD instance.
@@ -66,9 +68,14 @@ class FileWatch::Inotify::FD
66
68
  @watches = {}
67
69
  @buffer = FileWatch::StringPipeIO.new
68
70
 
69
- #@fd = CInotify.inotify_init1(INOTIFY_NONBLOCK)
71
+ # Can't use inotify_init1 since older kernels don't have it.
72
+ # Implement nonblock ourselves.
70
73
  @fd = CInotify.inotify_init()
71
74
 
75
+ # Track movement cookies # since 'moved_from' and 'moved_to' are separate
76
+ # events.
77
+ @movement = {}
78
+
72
79
  if java?
73
80
  @io = nil
74
81
  @rc = CInotify.fcntl(@fd, F_SETFL, O_NONBLOCK)
@@ -121,19 +128,23 @@ class FileWatch::Inotify::FD
121
128
  # fd.watch("/var/log/messages", :modify)
122
129
  public
123
130
  def watch(path, *what_to_watch)
124
- mask = what_to_watch.inject(0) { |m, val| m |= WATCH_BITS[val] }
131
+ mask = what_to_watch.uniq.inject(0) { |m, val| m |= WATCH_BITS[val] }
125
132
  watch_descriptor = CInotify.inotify_add_watch(@fd, path, mask)
126
133
 
127
134
  if watch_descriptor == -1
128
135
  raise FileWatch::Exception.new(
129
136
  "inotify_add_watch(#{@fd}, #{path}, #{mask}) failed. #{$?}", @fd, path)
130
137
  end
138
+
131
139
  @watches[watch_descriptor] = {
132
140
  :path => path,
133
141
  :partial => nil,
134
142
  :is_directory => File.directory?(path),
135
143
  }
136
- end
144
+ return watch_descriptor
145
+ end # def watch
146
+
147
+ # TODO(sissel): Implement inotify_rm_watch as 'cancel'
137
148
 
138
149
  private
139
150
  def normal_read(timeout=nil)
@@ -151,6 +162,7 @@ class FileWatch::Inotify::FD
151
162
 
152
163
  private
153
164
  def jruby_read(timeout=nil)
165
+ # TODO(sissel): instantiate this once to prevent extra memory usage? safe?
154
166
  @jruby_read_buffer = FFI::MemoryPointer.new(:char, 4096)
155
167
 
156
168
  # TODO(sissel): Block with select.
@@ -162,9 +174,13 @@ class FileWatch::Inotify::FD
162
174
  bytes = CInotify.read(@fd, @jruby_read_buffer, 4096)
163
175
 
164
176
  # read(2) returns -1 on error, which we expect to be EAGAIN, but...
165
- # TODO(sissel): maybe we should check errno properly...
166
- # Then again, errno isn't threadsafe, so we'd have to wrap this
167
- # in a critical block? Fun times
177
+ # TODO(sissel): maybe we should check errno properly... Then again,
178
+ # errno is supposedly thread-safe, but I believe that is in reference to
179
+ # pthreads. Ruby's green threads, fibers, etc, are not likely subject to
180
+ # the same safeties, so we have to assume errno is effed.
181
+ # This code is run in jruby only, so maybe we can guarantee that
182
+ # a thread is a pthread and has errno safety. Which leads us to the next
183
+ # question - how to access errno from jruby/ffi?
168
184
  break if bytes == -1
169
185
 
170
186
  @buffer.write(@jruby_read_buffer.get_bytes(0, bytes))
@@ -176,16 +192,19 @@ class FileWatch::Inotify::FD
176
192
  private
177
193
  def prepare(event)
178
194
  watch = @watches[event[:wd]]
179
- watchpath = watch[:path]
195
+
180
196
  if event.name == nil
181
- # Some events don't have the name at all, so add our own.
182
- event.name = watchpath
197
+ # Some events don't have the name at all, so add what we know.
198
+ # This usually occurs on events for files where inotify expects us to
199
+ # track the file name anyway.
200
+ event.name = watch[:path]
183
201
  event.type = :file
184
202
  else
185
- # Event paths are relative to the watch, if a directory. Prefix to make
186
- # the full path.
203
+ # In cases where we have event names, they are always(?) directory events
204
+ # and contain the name of the file changed. Event paths are relative to
205
+ # the watch, if a directory. Prefix to make the full path.
187
206
  if watch[:is_directory]
188
- event.name = File.join(watchpath, event.name)
207
+ event.name = File.join(watch[:path], event.name)
189
208
  event.type = :directory
190
209
  end
191
210
  end
@@ -239,6 +258,27 @@ class FileWatch::Inotify::FD
239
258
  loop do
240
259
  event = get
241
260
  break if event == nil
261
+
262
+ # inotify claims to guarantee order of events, so we should always see
263
+ # 'moved_from' events before 'moved_to'
264
+ if event.actions.include?(:moved_from)
265
+ @movement[event.cookie] = event.name
266
+ end
267
+
268
+ if event.actions.include?(:moved_to) and @movement.include?(event.cookie)
269
+ event.old_name = @movement[event.cookie]
270
+ @movement.delete(event.cookie)
271
+
272
+ # If we are watching this file, update the path with the new filename
273
+ @watches.each do |wd, watch|
274
+ if watch[:path] == event.old_name
275
+ watch[:old_path] = watch[:path]
276
+ watch[:path] = event.name
277
+ end
278
+ end # @watches.each
279
+ end # if event.actions.include? :moved_to
280
+
281
+ # We're dong mangling the event. Ship it.
242
282
  yield event
243
283
  end # loop
244
284
  end # def each
@@ -30,7 +30,7 @@ class FileWatch::TailGlob
30
30
  # :exclude => array of globs to ignore.
31
31
  public
32
32
  def tail(glob, options={}, &block)
33
- what_to_watch = [ :create, :modify, :delete ]
33
+ what_to_watch = [ :create, :modify, :delete_self, :move_self ]
34
34
 
35
35
  # Setup a callback specific to tihs tail call for handling
36
36
  # new files found; so we can attach options to each new file.
@@ -80,7 +80,7 @@ class FileWatch::TailGlob
80
80
  end
81
81
  end
82
82
 
83
- close(path) if @files.include?(path)
83
+ close(path) if following?(path)
84
84
  @files[path] = File.new(path, "r")
85
85
 
86
86
  # TODO(sissel): Support 'since'-like support.
@@ -102,6 +102,7 @@ class FileWatch::TailGlob
102
102
  @watch.subscribe do |event|
103
103
  path = event.name
104
104
 
105
+ p path => event.actions if $DEBUG
105
106
  event.actions.each do |action|
106
107
  method = "file_action_#{action}".to_sym
107
108
  if respond_to?(method)
@@ -166,10 +167,13 @@ class FileWatch::TailGlob
166
167
 
167
168
  protected
168
169
  def close(path)
169
- @files[path].close rescue nil
170
- @files.delete(path)
170
+ if following?(path)
171
+ p :CLOSE => path if $DEBUG
172
+ @files[path].close rescue nil
173
+ @files.delete(path)
174
+ end
171
175
  return nil
172
- end
176
+ end # def close
173
177
 
174
178
  protected
175
179
  def file_action_create(path, event, &block)
@@ -184,24 +188,54 @@ class FileWatch::TailGlob
184
188
  #@eof_actions[path] = :reopen
185
189
  else
186
190
  # If we are not yet watching this file, watch it.
187
- follow_file(path, :beginning)
188
-
189
- # Then read all of the data so far since this is a new file.
190
- file_action_modify(path, event, &block)
191
+ # Make sure this file matches a known glob
192
+ if @globoptions.find { |glob, opts| File.fnmatch?(glob, path) }
193
+ follow_file(path, :beginning)
194
+ # Then read all of the data so far since this is a new file.
195
+ file_action_modify(path, event, &block)
196
+ end
191
197
  end
192
198
  end # def file_action_create
193
199
 
194
- def file_action_delete(path, event, &block)
200
+ # This method is invoked when a file we are tailing is deleted.
201
+ protected
202
+ def file_action_delete_self(path, event, &block)
195
203
  close(path)
196
- # ignore
204
+ end # def file_action_delete_self
205
+
206
+ # This method is invoked when a file we are tailing is moved.
207
+ def file_action_move_self(path, event, &block)
208
+ # File renames are assumed to be rotations, so we close.
209
+ # if this file is not open, this close has no effect.
210
+ close(event.old_name)
197
211
  end
198
212
 
199
- def file_action_delete_self(path, event, &block)
200
- close(path)
201
- # ignore
202
- #p :delete_self => path
213
+ # Directory event; file in this dir was renamed.
214
+ protected
215
+ def file_action_moved_to(path, event, &block)
216
+ # TODO(sissel): ignore, maybe call close for good measure?
203
217
  end
204
218
 
219
+ protected
220
+ def file_action_moved_from(path, event, &block)
221
+ # ignore
222
+ end # def file_action_moved_from
223
+
224
+ protected
225
+ def file_action_move(path, event, &block)
226
+ # ignore
227
+ end # def file_action_move
228
+
229
+ protected
230
+ def file_action_move(path, event, &block)
231
+ # ignore
232
+ end # def file_action_move
233
+
234
+ protected
235
+ def file_action_delete(path, event, &block)
236
+ # ignore
237
+ end # def file_action_delete
238
+
205
239
  # Returns true if we are currently following the file at the given path.
206
240
  public
207
241
  def following?(path)
@@ -13,7 +13,7 @@ class FileWatch::Watch
13
13
 
14
14
  public
15
15
  def watch(path, *what_to_watch)
16
- @inotify.watch(path, *what_to_watch)
16
+ return @inotify.watch(path, *what_to_watch)
17
17
  end # def watch
18
18
 
19
19
  def subscribe(handler=nil, &block)
@@ -27,7 +27,7 @@ class FileWatch::WatchGlob
27
27
  begin
28
28
  next if watching.include?(path)
29
29
  @logger.info("Watching #{path}")
30
- @watch.watch(path, :create, :delete, :modify)
30
+ @watch.watch(path, :create, :delete, :modify, *what_to_watch)
31
31
  watching << path
32
32
 
33
33
  # Yield each file found by glob to the block.
@@ -51,7 +51,7 @@ class FileWatch::WatchGlob
51
51
  Dir.glob(globprefix).each do |path|
52
52
  next if watching.include?(path)
53
53
  @logger.info("Watching dir: #{path.inspect}")
54
- @watch.watch(path, :create)
54
+ @watch.watch(path, :create, :moved_to, :moved_from)
55
55
  @globdirs << path
56
56
  end # Dir.glob
57
57
  end # if part.include?("*")
@@ -64,7 +64,8 @@ class FileWatch::WatchGlob
64
64
  @watch.subscribe do |event|
65
65
  # If this event is a directory event and the file matches a watched glob,
66
66
  # then it should be a new-file creation event. Watch the file.
67
- if event.type == :directory and @globdirs.include?(File.dirname(event.name))
67
+ if event.type == :directory and event.actions.include?(:create) \
68
+ and @globdirs.include?(File.dirname(event.name))
68
69
  glob, what = @globs.find { |glob, what| File.fnmatch?(glob, event.name) }
69
70
  if glob
70
71
  @watch.watch(event.name, *what)
@@ -0,0 +1,5 @@
1
+ /home/jls/projects/ruby-filewatch/test/logrotate/*.log {
2
+ rotate 5
3
+ #compress
4
+ size 100
5
+ }
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: filewatch
3
3
  version: !ruby/object:Gem::Version
4
- hash: 31
4
+ hash: 29
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 4
10
- version: 0.2.4
9
+ - 5
10
+ version: 0.2.5
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jordan Sissel
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-04-13 00:00:00 -07:00
18
+ date: 2011-04-14 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -55,6 +55,7 @@ files:
55
55
  - lib/filewatch/buftok.rb
56
56
  - test/log4j/log4j.properties
57
57
  - test/log4j/LogTest.java
58
+ - test/logrotate/logrotate.conf
58
59
  - bin/gtail
59
60
  has_rdoc: true
60
61
  homepage: https://github.com/jordansissel/ruby-filewatch