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/globtail +55 -0
- data/lib/filewatch/buftok.rb +2 -2
- data/lib/filewatch/tail.rb +171 -48
- data/lib/filewatch/watch.rb +145 -26
- data/test/globtail/Makefile +7 -0
- data/test/globtail/framework.sh +58 -0
- data/test/globtail/test1.data +5 -0
- data/test/globtail/test1.sh +17 -0
- data/test/globtail/test10.data +4 -0
- data/test/globtail/test10.sh +20 -0
- data/test/globtail/test2.data +2 -0
- data/test/globtail/test2.sh +17 -0
- data/test/globtail/test3.data +3 -0
- data/test/globtail/test3.sh +18 -0
- data/test/globtail/test4.data +4 -0
- data/test/globtail/test4.sh +16 -0
- data/test/globtail/test5.data +6 -0
- data/test/globtail/test5.sh +25 -0
- data/test/globtail/test6.data +6 -0
- data/test/globtail/test6.sh +29 -0
- data/test/globtail/test7.data +5 -0
- data/test/globtail/test7.sh +24 -0
- data/test/globtail/test8.data +5 -0
- data/test/globtail/test8.sh +23 -0
- data/test/globtail/test9.data +3 -0
- data/test/globtail/test9.sh +22 -0
- metadata +38 -38
- data/bin/gtail +0 -50
- data/lib/filewatch/exception.rb +0 -12
- data/lib/filewatch/inotify/emhandler.rb +0 -16
- data/lib/filewatch/inotify/event.rb +0 -101
- data/lib/filewatch/inotify/fd.rb +0 -319
- data/lib/filewatch/namespace.rb +0 -3
- data/lib/filewatch/rubyfixes.rb +0 -8
- data/lib/filewatch/stringpipeio.rb +0 -33
- data/lib/filewatch/tailglob.rb +0 -244
- data/lib/filewatch/watchglob.rb +0 -83
- data/test/log4j/LogTest.java +0 -21
- data/test/log4j/log4j.properties +0 -20
- data/test/logrotate/logrotate.conf +0 -5
data/lib/filewatch/namespace.rb
DELETED
data/lib/filewatch/rubyfixes.rb
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
require "rubygems"
|
2
|
-
require "filewatch/namespace"
|
3
|
-
|
4
|
-
class FileWatch::StringPipeIO
|
5
|
-
def initialize
|
6
|
-
@buffer = ""
|
7
|
-
end # def initialize
|
8
|
-
|
9
|
-
def write(string)
|
10
|
-
@buffer += string
|
11
|
-
end # def write
|
12
|
-
|
13
|
-
def read(bytes=nil, nil_if_not_enough_data=false)
|
14
|
-
return nil if @buffer == nil || @buffer.empty?
|
15
|
-
|
16
|
-
if nil_if_not_enough_data && bytes > @buffer.size
|
17
|
-
return nil
|
18
|
-
end
|
19
|
-
|
20
|
-
bytes = @buffer.size if bytes == nil
|
21
|
-
data = @buffer[0 .. bytes]
|
22
|
-
if bytes <= @buffer.length
|
23
|
-
@buffer = @buffer[bytes .. -1]
|
24
|
-
else
|
25
|
-
@buffer = ""
|
26
|
-
end
|
27
|
-
return data
|
28
|
-
end # def read
|
29
|
-
|
30
|
-
def size
|
31
|
-
return @buffer.size
|
32
|
-
end
|
33
|
-
end # class Inotify::StringPipeIO
|
data/lib/filewatch/tailglob.rb
DELETED
@@ -1,244 +0,0 @@
|
|
1
|
-
require "filewatch/namespace"
|
2
|
-
require "filewatch/exception"
|
3
|
-
require "filewatch/watchglob"
|
4
|
-
require "logger"
|
5
|
-
|
6
|
-
class FileWatch::TailGlob
|
7
|
-
attr_accessor :logger
|
8
|
-
|
9
|
-
public
|
10
|
-
def initialize
|
11
|
-
@watch = FileWatch::WatchGlob.new
|
12
|
-
|
13
|
-
# hash of string path => File
|
14
|
-
@files = {}
|
15
|
-
@globoptions = {}
|
16
|
-
@options = {}
|
17
|
-
|
18
|
-
self.logger = Logger.new(STDERR)
|
19
|
-
# hash of string path => action to take on EOF
|
20
|
-
end # def initialize
|
21
|
-
|
22
|
-
def logger=(logger)
|
23
|
-
@logger = logger
|
24
|
-
@watch.logger = logger
|
25
|
-
end
|
26
|
-
|
27
|
-
# Watch a path glob.
|
28
|
-
#
|
29
|
-
# Options is a hash of:
|
30
|
-
# :exclude => array of globs to ignore.
|
31
|
-
public
|
32
|
-
def tail(glob, options={}, &block)
|
33
|
-
what_to_watch = [ :create, :modify, :delete_self, :move_self ]
|
34
|
-
|
35
|
-
# Setup a callback specific to tihs tail call for handling
|
36
|
-
# new files found; so we can attach options to each new file.
|
37
|
-
callback_set_options = lambda do |path|
|
38
|
-
@options[path] = options
|
39
|
-
end
|
40
|
-
|
41
|
-
# Save glob options
|
42
|
-
@globoptions[glob] = options
|
43
|
-
|
44
|
-
# callbacks can be an array of functions to call.
|
45
|
-
callbacks = [ callback_set_options ]
|
46
|
-
callbacks << block if block_given?
|
47
|
-
@globoptions[glob][:new_follow_callback] = callbacks
|
48
|
-
|
49
|
-
@watch.watch(glob, *what_to_watch) do |path|
|
50
|
-
# Save per-path options
|
51
|
-
callback_set_options.call(path)
|
52
|
-
|
53
|
-
# for each file found by the glob, open it.
|
54
|
-
follow_file(path, :end)
|
55
|
-
end # @watch.watch
|
56
|
-
end # def watch
|
57
|
-
|
58
|
-
private
|
59
|
-
def follow_file(path, seek=:end)
|
60
|
-
# Don't follow things that aren't files.
|
61
|
-
if !File.file?(path)
|
62
|
-
@logger.info "Skipping follow on #{path}, File.file? == false"
|
63
|
-
return
|
64
|
-
end
|
65
|
-
|
66
|
-
options = @options[path] || {}
|
67
|
-
if options.include?(:exclude)
|
68
|
-
options[:exclude].each do |exclusion|
|
69
|
-
if File.fnmatch?(exclusion, path)
|
70
|
-
@logger.info "Skipping #{path.inspect}, matches exclusion #{exclusion.inspect}"
|
71
|
-
return
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
if options.include?(:new_follow_callback)
|
77
|
-
options[:new_follow_callback].each do |callback|
|
78
|
-
#puts "Callback: #{callback.inspect}"
|
79
|
-
callback.call(path)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
close(path) if following?(path)
|
84
|
-
@files[path] = File.new(path, "r")
|
85
|
-
|
86
|
-
# TODO(sissel): Support 'since'-like support.
|
87
|
-
case seek
|
88
|
-
when :end; @files[path].sysseek(0, IO::SEEK_END)
|
89
|
-
when :beginning; # nothing
|
90
|
-
else
|
91
|
-
if seek.is_a?(Numeric)
|
92
|
-
# 'seek' could be a number that is an offset from
|
93
|
-
# the start of the file. We should seek to that point.
|
94
|
-
@files[path].sysseek(seek, IO::SEEK_SET)
|
95
|
-
end # seek.is_a?(Numeric)
|
96
|
-
end # case seek
|
97
|
-
end # def follow_file
|
98
|
-
|
99
|
-
public
|
100
|
-
def subscribe(handler=nil, &block)
|
101
|
-
# TODO(sissel): Add handler support.
|
102
|
-
@watch.subscribe do |event|
|
103
|
-
path = event.name
|
104
|
-
|
105
|
-
p path => event.actions if $DEBUG
|
106
|
-
event.actions.each do |action|
|
107
|
-
method = "file_action_#{action}".to_sym
|
108
|
-
if respond_to?(method)
|
109
|
-
send(method, path, event, &block)
|
110
|
-
else
|
111
|
-
$stderr.puts "Unsupported method #{self.class.name}##{method}"
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end # @watch.subscribe
|
115
|
-
end # def subscribe
|
116
|
-
|
117
|
-
protected
|
118
|
-
def file_action_modify(path, event, &block)
|
119
|
-
file = @files[path]
|
120
|
-
# Check if this path is in the exclude list.
|
121
|
-
# TODO(sissel): Is this check sufficient?
|
122
|
-
if file.nil?
|
123
|
-
@logger.info "Ignoring modify on '#{path}' - it's probably ignored'"
|
124
|
-
return
|
125
|
-
end
|
126
|
-
|
127
|
-
# Read until EOF, emitting each chunk read.
|
128
|
-
loop do
|
129
|
-
begin
|
130
|
-
data = file.sysread(4096)
|
131
|
-
yield event.name, data
|
132
|
-
rescue EOFError
|
133
|
-
check_for_truncation_or_deletion(path, event, &block)
|
134
|
-
break
|
135
|
-
end
|
136
|
-
end # loop
|
137
|
-
end # def file_action_modify
|
138
|
-
|
139
|
-
protected
|
140
|
-
def check_for_truncation_or_deletion(path, event, &block)
|
141
|
-
file = @files[path]
|
142
|
-
pos = file.sysseek(0, IO::SEEK_CUR)
|
143
|
-
#puts "EOF(#{path}), pos: #{pos}"
|
144
|
-
|
145
|
-
# Truncation is determined by comparing the current read position in the
|
146
|
-
# file against the size of the file. If the file shrank, than we should
|
147
|
-
# assume truncation and seek to the beginning.
|
148
|
-
begin
|
149
|
-
stat = file.stat
|
150
|
-
#p stat.size => pos
|
151
|
-
if stat.size < pos
|
152
|
-
# Truncated. Seek to beginning and read.
|
153
|
-
file.sysseek(0, IO::SEEK_SET)
|
154
|
-
file_action_modify(path, event, &block)
|
155
|
-
end
|
156
|
-
rescue Errno::ENOENT
|
157
|
-
# File was deleted or renamed. Stop following it.
|
158
|
-
close(path)
|
159
|
-
end
|
160
|
-
end # def follow_file
|
161
|
-
|
162
|
-
protected
|
163
|
-
def reopen(path)
|
164
|
-
close(path)
|
165
|
-
follow_file(path, :beginning)
|
166
|
-
end # def reopen
|
167
|
-
|
168
|
-
protected
|
169
|
-
def close(path)
|
170
|
-
if following?(path)
|
171
|
-
p :CLOSE => path if $DEBUG
|
172
|
-
@files[path].close rescue nil
|
173
|
-
@files.delete(path)
|
174
|
-
end
|
175
|
-
return nil
|
176
|
-
end # def close
|
177
|
-
|
178
|
-
protected
|
179
|
-
def file_action_create(path, event, &block)
|
180
|
-
if following?(path)
|
181
|
-
# TODO(sissel): If we are watching this file already, schedule it to be
|
182
|
-
# opened the next time we hit EOF on the current file descriptor for the
|
183
|
-
# same file. Maybe? Or is reopening now fine?
|
184
|
-
#
|
185
|
-
reopen(path)
|
186
|
-
|
187
|
-
# Schedule a reopen at EOF
|
188
|
-
#@eof_actions[path] = :reopen
|
189
|
-
else
|
190
|
-
# If we are not yet watching this file, watch it.
|
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
|
197
|
-
end
|
198
|
-
end # def file_action_create
|
199
|
-
|
200
|
-
# This method is invoked when a file we are tailing is deleted.
|
201
|
-
protected
|
202
|
-
def file_action_delete_self(path, event, &block)
|
203
|
-
close(path)
|
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)
|
211
|
-
end
|
212
|
-
|
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?
|
217
|
-
end
|
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
|
-
|
239
|
-
# Returns true if we are currently following the file at the given path.
|
240
|
-
public
|
241
|
-
def following?(path)
|
242
|
-
return @files.include?(path)
|
243
|
-
end # def following
|
244
|
-
end # class FileWatch::Tail
|
data/lib/filewatch/watchglob.rb
DELETED
@@ -1,83 +0,0 @@
|
|
1
|
-
require "filewatch/namespace"
|
2
|
-
require "filewatch/exception"
|
3
|
-
require "filewatch/watch"
|
4
|
-
|
5
|
-
class FileWatch::WatchGlob
|
6
|
-
attr_accessor :logger
|
7
|
-
|
8
|
-
# This class exists to wrap inotify, kqueue, periodic polling, etc,
|
9
|
-
# to provide you with a way to watch files and directories.
|
10
|
-
#
|
11
|
-
# For now, it only supports inotify.
|
12
|
-
def initialize
|
13
|
-
@watch = FileWatch::Watch.new
|
14
|
-
@globdirs = []
|
15
|
-
@globs = []
|
16
|
-
@logger = Logger.new(STDERR)
|
17
|
-
end
|
18
|
-
|
19
|
-
public
|
20
|
-
def watch(glob, *what_to_watch, &block)
|
21
|
-
@globs << [glob, what_to_watch]
|
22
|
-
|
23
|
-
watching = []
|
24
|
-
errors = []
|
25
|
-
paths = Dir.glob(glob)
|
26
|
-
paths.each do |path|
|
27
|
-
begin
|
28
|
-
next if watching.include?(path)
|
29
|
-
@logger.info("Watching #{path}")
|
30
|
-
@watch.watch(path, :create, :delete, :modify, *what_to_watch)
|
31
|
-
watching << path
|
32
|
-
|
33
|
-
# Yield each file found by glob to the block.
|
34
|
-
# This allows initialization on new files found at start.
|
35
|
-
yield path if block_given?
|
36
|
-
rescue FileWatch::Exception => e
|
37
|
-
@logger.info("Failed starting watch on #{path} - #{e}")
|
38
|
-
errors << e
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
# Go through the glob and look for paths leading into a '*'
|
43
|
-
splitpath = glob.split(File::SEPARATOR)
|
44
|
-
splitpath.each_with_index do |part, i|
|
45
|
-
current = File.join(splitpath[0 .. i])
|
46
|
-
current = "/" if current.empty?
|
47
|
-
next if watching.include?(current)
|
48
|
-
# TODO(sissel): Do better glob detection
|
49
|
-
if part.include?("*")
|
50
|
-
globprefix = File.join(splitpath[0 ... i])
|
51
|
-
Dir.glob(globprefix).each do |path|
|
52
|
-
next if watching.include?(path)
|
53
|
-
@logger.info("Watching dir: #{path.inspect}")
|
54
|
-
@watch.watch(path, :create, :moved_to, :moved_from)
|
55
|
-
@globdirs << path
|
56
|
-
end # Dir.glob
|
57
|
-
end # if part.include?("*")
|
58
|
-
end # splitpath.each_with_index
|
59
|
-
end # def watch
|
60
|
-
|
61
|
-
# TODO(sissel): implement 'unwatch' or cancel?
|
62
|
-
|
63
|
-
def subscribe(handler=nil, &block)
|
64
|
-
@watch.subscribe do |event|
|
65
|
-
# If this event is a directory event and the file matches a watched glob,
|
66
|
-
# then it should be a new-file creation event. Watch the file.
|
67
|
-
if event.type == :directory and event.actions.include?(:create) \
|
68
|
-
and @globdirs.include?(File.dirname(event.name))
|
69
|
-
glob, what = @globs.find { |glob, what| File.fnmatch?(glob, event.name) }
|
70
|
-
if glob
|
71
|
-
@watch.watch(event.name, *what)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
# Push the event to our callback.
|
76
|
-
block.call event
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
def each(&block)
|
81
|
-
@inotify.each(&block)
|
82
|
-
end # def each
|
83
|
-
end # class FileWatch::Watch
|
data/test/log4j/LogTest.java
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
import org.apache.log4j.Logger;
|
2
|
-
import org.apache.log4j.PropertyConfigurator;
|
3
|
-
|
4
|
-
public class LogTest {
|
5
|
-
private static Logger logger = Logger.getLogger(LogTest.class);
|
6
|
-
|
7
|
-
public static void main(String[] args) {
|
8
|
-
//BasicConfigurator.configure();
|
9
|
-
PropertyConfigurator.configure("log4j.properties");
|
10
|
-
|
11
|
-
int i = 0;
|
12
|
-
while (true) {
|
13
|
-
logger.info("Testing: " + i);
|
14
|
-
i++;
|
15
|
-
try {
|
16
|
-
Thread.sleep(100);
|
17
|
-
} catch (Exception e) {
|
18
|
-
}
|
19
|
-
}
|
20
|
-
} /* public static void main(String[]) */
|
21
|
-
} /* public class LogTest */
|
data/test/log4j/log4j.properties
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
log4j.rootLogger=INFO,stdout,rolling
|
2
|
-
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
3
|
-
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
4
|
-
log4j.appender.stdout.layout.ConversionPattern=%5p %d{HH:mm:ss,SSS} %m%n
|
5
|
-
|
6
|
-
log4j.appender.rolling=org.apache.log4j.RollingFileAppender
|
7
|
-
log4j.appender.rolling.maxFileSize=1000
|
8
|
-
log4j.appender.rolling.maxBackupIndex=10
|
9
|
-
log4j.appender.rolling.layout=org.apache.log4j.PatternLayout
|
10
|
-
log4j.appender.rolling.layout.ConversionPattern=%5p [%t] %d{ISO8601} %F (line %L) %m%n
|
11
|
-
log4j.appender.rolling.File=logs/logtest.log
|
12
|
-
log4j.appender.rolling.DatePattern=.yyyy-MM-dd.HH-mm-ss
|
13
|
-
|
14
|
-
#log4j.appender.D=org.apache.log4j.DailyRollingFileAppender
|
15
|
-
#log4j.appender.D.layout=org.apache.log4j.PatternLayout
|
16
|
-
#log4j.appender.D.layout=org.apache.log4j.JSONLayout
|
17
|
-
#log4j.appender.D.layout.ConversionPattern=%5p [%t] %d{ISO8601} %F (line %L) %m%n
|
18
|
-
#log4j.appender.D.File=logs/logtest.log
|
19
|
-
#log4j.appender.D.DatePattern=.yyyy-MM-dd.HH-mm-ss
|
20
|
-
|