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.
- data/lib/filewatch/inotify/event.rb +12 -1
- data/lib/filewatch/inotify/fd.rb +56 -16
- data/lib/filewatch/tailglob.rb +49 -15
- data/lib/filewatch/watch.rb +1 -1
- data/lib/filewatch/watchglob.rb +4 -3
- data/test/logrotate/logrotate.conf +5 -0
- metadata +5 -4
@@ -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
|
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
|
|
data/lib/filewatch/inotify/fd.rb
CHANGED
@@ -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
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
167
|
-
#
|
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
|
-
|
195
|
+
|
180
196
|
if event.name == nil
|
181
|
-
# Some events don't have the name at all, so add
|
182
|
-
|
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
|
-
#
|
186
|
-
# the
|
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(
|
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
|
data/lib/filewatch/tailglob.rb
CHANGED
@@ -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, :
|
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
|
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
|
-
|
170
|
-
|
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
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
200
|
-
|
201
|
-
|
202
|
-
#
|
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)
|
data/lib/filewatch/watch.rb
CHANGED
data/lib/filewatch/watchglob.rb
CHANGED
@@ -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
|
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)
|
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:
|
4
|
+
hash: 29
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
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-
|
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
|