filewatch 0.2.5 → 0.3.0

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/bin/gtail DELETED
@@ -1,50 +0,0 @@
1
- #!/usr/bin/env ruby
2
- require "rubygems"
3
- require "filewatch/tailglob"
4
- require "filewatch/buftok"
5
- require "optparse"
6
-
7
- def main(args)
8
- with_filenames = true
9
- exclude_patterns = []
10
-
11
- opts = OptionParser.new do |opts|
12
- opts.banner = "Usage: #{$0} [options] <path_or_glob> [path_or_glob2] [...]"
13
-
14
- opts.on("-n", "--no-filename",
15
- "Supress prefixing of output with file names") do |x|
16
- with_filenames = false
17
- end # -n
18
-
19
- opts.on("-x EXCLUDE", "--exclude EXCLUDE",
20
- "A pattern to ignore. Wildcard/globs accepted." \
21
- " Can be specified multiple times") do |pattern|
22
- exclude_patterns << pattern
23
- end
24
- end # OptionParser
25
-
26
- opts.parse!(args)
27
-
28
- if args.length == 0
29
- puts opts.banner
30
- return 1
31
- end
32
-
33
- tail = FileWatch::TailGlob.new
34
- ARGV.each do |path|
35
- tail.tail(path, :exclude => exclude_patterns)
36
- end
37
-
38
- buffer = BufferedTokenizer.new
39
- tail.subscribe do |path, data|
40
- buffer.extract(data).each do |line|
41
- if with_filenames
42
- puts "#{path}: #{line}"
43
- else
44
- puts line
45
- end
46
- end # buffer.extract
47
- end # tail.subscribe
48
- end # def main
49
-
50
- exit(main(ARGV))
@@ -1,12 +0,0 @@
1
- require "filewatch/namespace"
2
-
3
- class FileWatch::Exception < Exception
4
- attr_accessor :fd
5
- attr_accessor :path
6
-
7
- def initialize(message, fd, path)
8
- super(message)
9
- @fd = fd
10
- @path = path
11
- end
12
- end
@@ -1,16 +0,0 @@
1
- require "filewatch/inotify/fd"
2
- require "filewatch/namespace"
3
-
4
- class FileWatch::Inotify::EMHandler < EventMachine::Connection
5
- def initialize(inotify_fd, callback=nil)
6
- @inotify = inotify_fd
7
- @callback = callback
8
- self.notify_readable = true
9
- end
10
-
11
- def notify_readable
12
- @inotify.each do |event|
13
- @callback.call(event)
14
- end
15
- end
16
- end
@@ -1,101 +0,0 @@
1
- require "ffi"
2
- require "filewatch/inotify/fd"
3
- require "filewatch/namespace"
4
- require "filewatch/rubyfixes"
5
-
6
- class FileWatch::Inotify::Event < FFI::Struct
7
- layout :wd, :int,
8
- :mask, :uint32,
9
- :cookie, :uint32,
10
- :len, :uint32
11
- # last piece is the name, but don't hold it in the struct
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
19
-
20
- attr_accessor :name
21
-
22
- # This attribute is only set for file renames
23
- attr_accessor :old_name
24
-
25
- # Enum of :directory or :file
26
- attr_accessor :type
27
-
28
- def initialize(pointer)
29
- if pointer.is_a?(String)
30
- pointer = FFI::MemoryPointer.from_string(pointer)
31
- end
32
-
33
- super(pointer)
34
- end
35
-
36
- def self.from_stringpipeio(io)
37
- # I implemented a 'string pipe' IO because normal "buffered" IO reads
38
- # on inotify fds fails in ruby 1.9.2 because it literally calls read(2)
39
- # with 'self.size' as the byte size to read. This causes EINVAL from
40
- # inotify, documented thusly in inotify(7):
41
- #
42
- # """ The behavior when the buffer given to read(2) is too small to
43
- # return information about the next event depends on the kernel
44
- # version: in kernels before 2.6.21, read(2) returns 0; since
45
- # kernel 2.6.21, read(2) fails with the error EINVAL. """
46
- #
47
- # Working around this requires implementing our own read buffering
48
- # unless comeone clues me in on how to make ruby 1.9.2 read larger
49
- # blocks and actually do the nice buffered IO we've all come to
50
- # know and love.
51
-
52
- begin
53
- data = io.read(self.size, true)
54
- rescue Errno::EINVAL => e
55
- $stderr.puts "Read was too small? Confused."
56
- raise e
57
- end
58
-
59
- return nil if data == nil
60
-
61
- pointer = FFI::MemoryPointer.from_string(data)
62
- event = self.new(pointer)
63
-
64
- event.from_stringpipeio(io)
65
- return event
66
- end
67
-
68
- def from_stringpipeio(io)
69
- begin
70
- if self[:len] > 0
71
- @name = io.read(self[:len], true)
72
- else
73
- @name = nil
74
- end
75
- rescue Errno::EINVAL => e
76
- $stderr.puts "Read was too small? Confused."
77
- raise e
78
- end
79
- return self if @name == nil
80
-
81
- @name = @name.split("\0", 2).first
82
-
83
- return self
84
- end
85
-
86
- def actions
87
- # TODO(sissel): Skip these?
88
- ::FileWatch::Inotify::FD::WATCH_BITS.reject do |key, bitmask|
89
- #p key => [self.mask, bitmask] if self.mask & bitmask != 0
90
- self.mask & bitmask == 0
91
- end.keys
92
- end
93
-
94
- def to_s
95
- return "#{@name} (#{self.actions.join(", ")}) [type=#{type}]"
96
- end
97
-
98
- def partial?
99
- return self[:len] > 0 && @name == nil
100
- end # def partial?
101
- end
@@ -1,319 +0,0 @@
1
- require "rubygems"
2
- require "ffi"
3
- require "fcntl"
4
- require "filewatch/exception"
5
- require "filewatch/inotify/event"
6
- require "filewatch/namespace"
7
- require "filewatch/rubyfixes"
8
- require "filewatch/stringpipeio"
9
-
10
- class FileWatch::Inotify::FD
11
- include Enumerable
12
-
13
- module CInotify
14
- extend FFI::Library
15
- ffi_lib FFI::Library::LIBC
16
-
17
- attach_function :inotify_init, [], :int
18
- attach_function :fcntl, [:int, :int, :long], :int
19
- attach_function :inotify_add_watch, [:int, :string, :uint32], :int
20
-
21
- # So we can read and poll inotify from jruby.
22
- attach_function :read, [:int, :pointer, :size_t], :int
23
-
24
- # Poll is pretty crappy, but it's better than nothing.
25
- attach_function :poll, [:pointer, :int, :int], :int
26
- end
27
-
28
- INOTIFY_CLOEXEC = 02000000
29
- INOTIFY_NONBLOCK = 04000
30
-
31
- F_SETFL = 4
32
- O_NONBLOCK = 04000
33
-
34
- WATCH_BITS = {
35
- :access => 1 << 0,
36
- :modify => 1 << 1,
37
- :attrib => 1 << 2,
38
- :close_write => 1 << 3,
39
- :close_nowrite => 1 << 4,
40
- :open => 1 << 5,
41
- :moved_from => 1 << 6,
42
- :moved_to => 1 << 7,
43
- :create => 1 << 8,
44
- :delete => 1 << 9,
45
- :delete_self => 1 << 10,
46
- :move_self => 1 << 11,
47
- }
48
-
49
- # Shortcuts
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]
54
-
55
- attr_reader :fd
56
-
57
- public
58
- def self.can_watch?(filestat)
59
- # TODO(sissel): implement.
60
- return true
61
- end # def self.can_watch?
62
-
63
- # Create a new FileWatch::Inotify::FD instance.
64
- # This is the main interface you want to use for watching
65
- # files, directories, etc.
66
- public
67
- def initialize
68
- @watches = {}
69
- @buffer = FileWatch::StringPipeIO.new
70
-
71
- # Can't use inotify_init1 since older kernels don't have it.
72
- # Implement nonblock ourselves.
73
- @fd = CInotify.inotify_init()
74
-
75
- # Track movement cookies # since 'moved_from' and 'moved_to' are separate
76
- # events.
77
- @movement = {}
78
-
79
- if java?
80
- @io = nil
81
- @rc = CInotify.fcntl(@fd, F_SETFL, O_NONBLOCK)
82
- if @rc == -1
83
- raise FileWatch::Exception.new(
84
- "fcntl(#{@fd}, F_SETFL, O_NONBLOCK) failed. #{$?}", @fd, nil)
85
- end
86
- else
87
- @io = IO.for_fd(@fd)
88
- @io.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK)
89
- end
90
- end
91
-
92
- # Are we using java?
93
- private
94
- def java?
95
- return RUBY_PLATFORM == "java"
96
- end
97
-
98
- # Add a watch.
99
- # - path is a string file path
100
- # - what_to_watch is any of the valid WATCH_BITS keys
101
- #
102
- # Possible values for what_to_watch:
103
- # (See also the inotify(7) manpage under 'inotify events'
104
- #
105
- # :access - file was accesssed (a read)
106
- # :attrib - permissions, timestamps, link count, owner, etc changed
107
- # :close_nowrite - a file not opened for writing was closed
108
- # :close_write - a file opened for writing was closed
109
- # :create - file/directory was created, only valid on watched directories
110
- # :delete - file/directory was deleted, only valid on watched directories
111
- # :delete_self - a watched file or directory was deleted
112
- # :modify - A watched file was modified
113
- # :moved_from - A file was moved out of a watched directory
114
- # :moved_to - A file was moved into a watched directory
115
- # :move_self - A watched file/directory was moved
116
- # :open - A file was opened
117
- # # Shortcuts
118
- # :close - close_nowrite or close_write
119
- # :move - move_self or moved_from or moved_to
120
- # :delete - delete or delete_self
121
- #
122
- # (Some of the above data copied from the inotify(7) manpage, which comes from
123
- # the linux man-pages project. http://www.kernel.org/doc/man-pages/ )
124
- #
125
- # Example:
126
- # fd = FileWatch::Inotify::FD.new
127
- # fd.watch("/tmp", :create, :delete)
128
- # fd.watch("/var/log/messages", :modify)
129
- public
130
- def watch(path, *what_to_watch)
131
- mask = what_to_watch.uniq.inject(0) { |m, val| m |= WATCH_BITS[val] }
132
- watch_descriptor = CInotify.inotify_add_watch(@fd, path, mask)
133
-
134
- if watch_descriptor == -1
135
- raise FileWatch::Exception.new(
136
- "inotify_add_watch(#{@fd}, #{path}, #{mask}) failed. #{$?}", @fd, path)
137
- end
138
-
139
- @watches[watch_descriptor] = {
140
- :path => path,
141
- :partial => nil,
142
- :is_directory => File.directory?(path),
143
- }
144
- return watch_descriptor
145
- end # def watch
146
-
147
- # TODO(sissel): Implement inotify_rm_watch as 'cancel'
148
-
149
- private
150
- def normal_read(timeout=nil)
151
- loop do
152
- begin
153
- data = @io.sysread(4096)
154
- @buffer.write(data)
155
- rescue Errno::EAGAIN
156
- # No data left to read, moveon.
157
- break
158
- end
159
- end
160
- return nil
161
- end # def normal_read
162
-
163
- private
164
- def jruby_read(timeout=nil)
165
- # TODO(sissel): instantiate this once to prevent extra memory usage? safe?
166
- @jruby_read_buffer = FFI::MemoryPointer.new(:char, 4096)
167
-
168
- # TODO(sissel): Block with select.
169
- # Will have to use FFI to call select, too.
170
-
171
- # We have to call libc's read(2) because JRuby/Java can't trivially
172
- # be told about existing file descriptors.
173
- loop do
174
- bytes = CInotify.read(@fd, @jruby_read_buffer, 4096)
175
-
176
- # read(2) returns -1 on error, which we expect to be EAGAIN, but...
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?
184
- break if bytes == -1
185
-
186
- @buffer.write(@jruby_read_buffer.get_bytes(0, bytes))
187
- end
188
- return nil
189
- end # def jruby_read
190
-
191
- # Make any necessary corrections to the event
192
- private
193
- def prepare(event)
194
- watch = @watches[event[:wd]]
195
-
196
- if event.name == nil
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]
201
- event.type = :file
202
- else
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.
206
- if watch[:is_directory]
207
- event.name = File.join(watch[:path], event.name)
208
- event.type = :directory
209
- end
210
- end
211
-
212
- return event
213
- end # def prepare
214
-
215
- # Get one inotify event.
216
- #
217
- # TIMEOUT NOT SUPPORTED YET;
218
- #
219
- # If timeout is not given, this call blocks.
220
- # If a timeout occurs and no event was read, nil is returned.
221
- #
222
- # Returns nil on timeout or an FileWatch::Inotify::Event on success.
223
- private
224
- def get(timeout_not_supported_yet=nil)
225
- # This big 'loop' is to support pop { |event| ... } shipping each available event.
226
- # It's not very rubyish (we should probably use Enumerable and such.
227
- if java?
228
- #jruby_read(timeout)
229
- jruby_read
230
- else
231
- #normal_read(timeout)
232
- normal_read
233
- end
234
-
235
- # Recover any previous partial event.
236
- if @partial
237
- event = @partial.from_stringpipeio(@buffer)
238
- else
239
- event = FileWatch::Inotify::Event.from_stringpipeio(@buffer)
240
- return nil if event == nil
241
- end
242
-
243
- if event.partial?
244
- @partial = event
245
- return nil
246
- end
247
- @partial = nil
248
-
249
- return prepare(event)
250
- end # def get
251
-
252
- # For Enumerable support
253
- #
254
- # Yields one FileWatch::Inotify::Event per iteration. If there are no more events
255
- # at the this time, then this method will end.
256
- public
257
- def each(&block)
258
- loop do
259
- event = get
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.
282
- yield event
283
- end # loop
284
- end # def each
285
-
286
- # Subscribe to inotify events for this instance.
287
- #
288
- # If you are running in EventMachine, this will set up a
289
- # subscription that behaves sanely in EventMachine
290
- #
291
- # If you are not running in EventMachine, this method
292
- # blocks forever, invoking the given block for each event.
293
- # Further, if you are not using EventMachine, you should
294
- # not pass a handler, only a block, like this:
295
- #
296
- # fd.subscribe do |event|
297
- # puts event
298
- # end
299
- public
300
- def subscribe(handler=nil, &block)
301
- if defined?(EventMachine) && EventMachine.reactor_running?
302
- require "filewatch/inotify/emhandler"
303
- handler = FileWatch::Inotify::EMHandler if handler == nil
304
- EventMachine::watch(@fd, handler, self, block)
305
- else
306
- loop do
307
- if java?
308
- # No way to select on FFI-derived file descriptors yet,
309
- # when I grab poll(2) via FFI, this sleep will become
310
- # a poll or sleep invocation.
311
- sleep(1)
312
- else
313
- IO.select([@io], nil, nil, nil)
314
- end
315
- each(&block)
316
- end
317
- end
318
- end
319
- end # class FileWatch::Inotify::FD