filewatch 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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