eventmachine-tail 0.6.4 → 0.6.6
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.
- checksums.yaml +7 -0
- data/lib/em/filetail.rb +21 -20
- data/lib/em/globwatcher.rb +18 -1
- data/samples/globwatch.rb +4 -0
- data/test/test_filetail.rb +24 -7
- data/test/test_glob.rb +44 -0
- metadata +33 -26
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 96b3872cfc6a1c388aa9ee4d8f67ae2b69cc7a9ae5fdbb39449a9a293702fb37
|
4
|
+
data.tar.gz: c268790026a0f65cff3d521dcd366db1ab848f685f4ccd9aa69c024bb09bf406
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 15956a61dd9222dd09147623d7b8357850d2995ed5a270039b326e623d33916ca1fd2321920a1334ce5a4398a0db49e4134f32dacb1148153cb133cc202281da
|
7
|
+
data.tar.gz: 507f39f080224e3784e23bcaf1ba8c680f54d47e277fd134c8becb30089aa33b415eb4e9e0cbe035ec30c8d3a8598ca1505d715a63ec40295fc376c329e5abdc
|
data/lib/em/filetail.rb
CHANGED
@@ -35,10 +35,10 @@ EventMachine.kqueue = true if EventMachine.kqueue?
|
|
35
35
|
# See also: EventMachine::FileTail#receive_data
|
36
36
|
class EventMachine::FileTail
|
37
37
|
# Maximum size to read at a time from a single file.
|
38
|
-
CHUNKSIZE = 65536
|
38
|
+
CHUNKSIZE = 65536
|
39
39
|
|
40
40
|
#MAXSLEEP = 2
|
41
|
-
|
41
|
+
|
42
42
|
FORCE_ENCODING = !! (defined? Encoding)
|
43
43
|
|
44
44
|
# The path of the file being tailed
|
@@ -46,7 +46,7 @@ class EventMachine::FileTail
|
|
46
46
|
|
47
47
|
# The current file read position
|
48
48
|
attr_reader :position
|
49
|
-
|
49
|
+
|
50
50
|
# If this tail is closed
|
51
51
|
attr_reader :closed
|
52
52
|
|
@@ -57,11 +57,11 @@ class EventMachine::FileTail
|
|
57
57
|
# Check interval for looking for a file if we are tailing it and it has
|
58
58
|
# gone missing.
|
59
59
|
attr_accessor :missing_file_check_interval
|
60
|
-
|
60
|
+
|
61
61
|
# Tail a file
|
62
62
|
#
|
63
63
|
# * path is a string file path to tail
|
64
|
-
# * startpos is an offset to start tailing the file at. If -1, start at end of
|
64
|
+
# * startpos is an offset to start tailing the file at. If -1, start at end of
|
65
65
|
# file.
|
66
66
|
#
|
67
67
|
# If you want debug messages, run ruby with '-d' or set $DEBUG
|
@@ -102,6 +102,8 @@ class EventMachine::FileTail
|
|
102
102
|
|
103
103
|
EventMachine::next_tick do
|
104
104
|
open
|
105
|
+
next unless @file
|
106
|
+
|
105
107
|
if (startpos == -1)
|
106
108
|
@position = @file.sysseek(0, IO::SEEK_END)
|
107
109
|
# TODO(sissel): if we don't have inotify or kqueue, should we
|
@@ -116,7 +118,7 @@ class EventMachine::FileTail
|
|
116
118
|
end # EventMachine::next_tick
|
117
119
|
end # def initialize
|
118
120
|
|
119
|
-
# This method is called when a tailed file has data read.
|
121
|
+
# This method is called when a tailed file has data read.
|
120
122
|
#
|
121
123
|
# * data - string data read from the file.
|
122
124
|
#
|
@@ -132,7 +134,7 @@ class EventMachine::FileTail
|
|
132
134
|
# @buffer.extract(data).each do |line|
|
133
135
|
# # do something with 'line'
|
134
136
|
# end
|
135
|
-
# end
|
137
|
+
# end
|
136
138
|
public
|
137
139
|
def receive_data(data)
|
138
140
|
if @handler # FileTail.new called with a block
|
@@ -171,7 +173,6 @@ class EventMachine::FileTail
|
|
171
173
|
schedule_next_read
|
172
174
|
elsif status == :moved
|
173
175
|
# read to EOF, then reopen.
|
174
|
-
@reopen_on_eof = true
|
175
176
|
schedule_next_read
|
176
177
|
elsif status == :unbind
|
177
178
|
# :unbind is called after the :deleted handler
|
@@ -186,7 +187,7 @@ class EventMachine::FileTail
|
|
186
187
|
def open
|
187
188
|
return if @closed
|
188
189
|
@file.close if @file && !@file.closed?
|
189
|
-
return unless File.
|
190
|
+
return unless File.exist?(@path)
|
190
191
|
begin
|
191
192
|
@logger.debug "Opening file #{@path}"
|
192
193
|
@file = File.open(@path, "r")
|
@@ -214,19 +215,19 @@ class EventMachine::FileTail
|
|
214
215
|
@file.close if @file
|
215
216
|
end
|
216
217
|
end # def close
|
217
|
-
|
218
|
+
|
218
219
|
# More rubyesque way of checking if this tail is closed
|
219
220
|
public
|
220
221
|
def closed?
|
221
222
|
@closed
|
222
223
|
end
|
223
|
-
|
224
|
+
|
224
225
|
# Watch our file.
|
225
226
|
private
|
226
227
|
def watch
|
227
228
|
@watch.stop_watching if @watch
|
228
229
|
@symlink_timer.cancel if @symlink_timer
|
229
|
-
return unless File.
|
230
|
+
return unless File.exist?(@path)
|
230
231
|
|
231
232
|
@logger.debug "Starting watch on #{@path}"
|
232
233
|
callback = proc { |what| notify(what) }
|
@@ -354,19 +355,19 @@ class EventMachine::FileTail
|
|
354
355
|
def read_file_metadata(&block)
|
355
356
|
begin
|
356
357
|
filestat = File.stat(@path)
|
358
|
+
symlink_stat = nil
|
359
|
+
symlink_target = nil
|
360
|
+
|
361
|
+
if filestat.symlink?
|
362
|
+
symlink_stat = File.lstat(@path) rescue nil
|
363
|
+
symlink_target = File.readlink(@path) rescue nil
|
364
|
+
end
|
357
365
|
rescue Errno::ENOENT
|
358
366
|
raise
|
359
367
|
rescue => e
|
360
368
|
@logger.debug("File stat on '#{@path}' failed")
|
361
369
|
on_exception e
|
362
370
|
end
|
363
|
-
symlink_stat = nil
|
364
|
-
symlink_target = nil
|
365
|
-
|
366
|
-
if File.symlink?(@path)
|
367
|
-
symlink_stat = File.lstat(@path) rescue nil
|
368
|
-
symlink_target = File.readlink(@path) rescue nil
|
369
|
-
end
|
370
371
|
|
371
372
|
if block_given?
|
372
373
|
yield filestat, symlink_stat, symlink_target
|
@@ -393,7 +394,7 @@ class EventMachine::FileTail
|
|
393
394
|
@logger.debug "Symlink target changed. Reopening..."
|
394
395
|
@reopen_on_eof = true
|
395
396
|
schedule_next_read
|
396
|
-
end
|
397
|
+
end
|
397
398
|
elsif (filestat.ino != @filestat.ino or filestat.rdev != @filestat.rdev)
|
398
399
|
@logger.debug "Inode or device changed. Reopening..."
|
399
400
|
@logger.debug filestat
|
data/lib/em/globwatcher.rb
CHANGED
@@ -81,6 +81,15 @@ class EventMachine::FileGlobWatch
|
|
81
81
|
"module?")
|
82
82
|
end # def file_found
|
83
83
|
|
84
|
+
# This method is called when a file is modified.
|
85
|
+
#
|
86
|
+
# * path - The string path of the file modified
|
87
|
+
#
|
88
|
+
# You AREN'T required to implement this in your sublcass or module
|
89
|
+
public
|
90
|
+
def file_modified(path)
|
91
|
+
end
|
92
|
+
|
84
93
|
# This method is called when a file is deleted.
|
85
94
|
#
|
86
95
|
# * path - the string path of the file deleted
|
@@ -104,7 +113,10 @@ class EventMachine::FileGlobWatch
|
|
104
113
|
fileinfo = FileInfo.new(path) rescue next
|
105
114
|
# Skip files that have the same inode (renamed or hardlinked)
|
106
115
|
known_files.delete(fileinfo.stat.ino)
|
107
|
-
|
116
|
+
if @files.include?(fileinfo.stat.ino)
|
117
|
+
next unless modified(fileinfo)
|
118
|
+
file_modified(path)
|
119
|
+
end
|
108
120
|
|
109
121
|
track(fileinfo)
|
110
122
|
file_found(path)
|
@@ -144,6 +156,11 @@ class EventMachine::FileGlobWatch
|
|
144
156
|
#end
|
145
157
|
end # def watch
|
146
158
|
|
159
|
+
# Tells if a file has been modified since last time
|
160
|
+
def modified(fileinfo)
|
161
|
+
not @files[fileinfo.stat.ino].stat.mtime == fileinfo.stat.mtime
|
162
|
+
end
|
163
|
+
|
147
164
|
private
|
148
165
|
class FileWatcher < EventMachine::FileWatch
|
149
166
|
def initialize(globwatch, &block)
|
data/samples/globwatch.rb
CHANGED
data/test/test_filetail.rb
CHANGED
@@ -26,11 +26,11 @@ class Reader < EventMachine::FileTail
|
|
26
26
|
@buffer.extract(data).each do |line|
|
27
27
|
@lineno += 1
|
28
28
|
expected = @data.shift
|
29
|
-
@testobj.assert_equal(expected, line,
|
29
|
+
@testobj.assert_equal(expected, line,
|
30
30
|
"Expected '#{expected}' on line #{@lineno}, but got '#{line}'")
|
31
31
|
end # @buffer.extract
|
32
32
|
end # def receive_data
|
33
|
-
|
33
|
+
|
34
34
|
# This effectively tests EOF handling by requiring it to work in order
|
35
35
|
# for the tests to pass.
|
36
36
|
def eof
|
@@ -63,6 +63,23 @@ class TestFileTail < Test::Unit::TestCase
|
|
63
63
|
end # EM.run
|
64
64
|
end # def test_filetail
|
65
65
|
|
66
|
+
def test_filetail_close
|
67
|
+
tmp = Tempfile.new("testfiletail")
|
68
|
+
data = DATA.clone
|
69
|
+
data.each { |i| tmp.puts i }
|
70
|
+
tmp.flush
|
71
|
+
|
72
|
+
EM.run do
|
73
|
+
abort_after_timeout(2)
|
74
|
+
|
75
|
+
ft = EM::file_tail(tmp.path, Reader, -1, self)
|
76
|
+
ft.close
|
77
|
+
timer = EM::PeriodicTimer.new(0.2) do
|
78
|
+
timer.cancel and finish if ft.closed?
|
79
|
+
end
|
80
|
+
end # EM.run
|
81
|
+
end # def test_filetail_close
|
82
|
+
|
66
83
|
def test_filetail_with_seek
|
67
84
|
tmp = Tempfile.new("testfiletail")
|
68
85
|
data = DATA.clone
|
@@ -86,7 +103,7 @@ class TestFileTail < Test::Unit::TestCase
|
|
86
103
|
EM::file_tail(tmp.path) do |filetail, line|
|
87
104
|
lineno += 1
|
88
105
|
expected = data.shift
|
89
|
-
assert_equal(expected, line,
|
106
|
+
assert_equal(expected, line,
|
90
107
|
"Expected '#{expected}' on line #{@lineno}, but got '#{line}'")
|
91
108
|
finish if data.length == 0
|
92
109
|
end
|
@@ -120,7 +137,7 @@ class TestFileTail < Test::Unit::TestCase
|
|
120
137
|
lineno += 1
|
121
138
|
expected = data.shift
|
122
139
|
#puts "Got #{lineno}: #{line}"
|
123
|
-
assert_equal(expected, line,
|
140
|
+
assert_equal(expected, line,
|
124
141
|
"Expected '#{expected}' on line #{lineno}, but got '#{line}'")
|
125
142
|
finish if data.length == 0
|
126
143
|
|
@@ -176,7 +193,7 @@ class TestFileTail < Test::Unit::TestCase
|
|
176
193
|
lineno += 1
|
177
194
|
expected = data.shift
|
178
195
|
puts "Got #{lineno}: #{line}" if $debug
|
179
|
-
assert_equal(expected, line,
|
196
|
+
assert_equal(expected, line,
|
180
197
|
"Expected '#{expected}' on line #{lineno}, but got '#{line}'")
|
181
198
|
finish if data.length == 0
|
182
199
|
|
@@ -208,7 +225,7 @@ class TestFileTail < Test::Unit::TestCase
|
|
208
225
|
File.delete(f.path)
|
209
226
|
end
|
210
227
|
end # def test_filetail_tracks_renames
|
211
|
-
|
228
|
+
|
212
229
|
def test_encoding
|
213
230
|
return if RUBY_VERSION < '1.9.0'
|
214
231
|
tmp = Tempfile.new("testfiletail")
|
@@ -217,7 +234,7 @@ class TestFileTail < Test::Unit::TestCase
|
|
217
234
|
abort_after_timeout(1)
|
218
235
|
|
219
236
|
EM::file_tail(tmp.path) do |filetail, line|
|
220
|
-
assert_equal(Encoding.default_external, line.encoding,
|
237
|
+
assert_equal(Encoding.default_external, line.encoding,
|
221
238
|
"Expected the read data to have the encoding specified in Encoding.default_external (#{Encoding.default_external}, but was #{line.encoding})")
|
222
239
|
finish
|
223
240
|
end
|
data/test/test_glob.rb
CHANGED
@@ -32,6 +32,26 @@ class Watcher < EventMachine::FileGlobWatch
|
|
32
32
|
end
|
33
33
|
end # class Reader
|
34
34
|
|
35
|
+
class ModificationWatcher < EventMachine::FileGlobWatch
|
36
|
+
def initialize(path, interval, data, testobj)
|
37
|
+
super(path, interval)
|
38
|
+
@data = data
|
39
|
+
@testobj = testobj
|
40
|
+
end # def initialize
|
41
|
+
|
42
|
+
def file_found(path)
|
43
|
+
end
|
44
|
+
|
45
|
+
def file_deleted(patH)
|
46
|
+
end
|
47
|
+
|
48
|
+
def file_modified(path)
|
49
|
+
@testobj.assert(@data.include?(path), "Expected #{path} in \n#{@data.join("\n")}")
|
50
|
+
@data.delete(path)
|
51
|
+
@testobj.finish if @data.length == 0
|
52
|
+
end
|
53
|
+
end # class ModificationWatcher
|
54
|
+
|
35
55
|
class TestGlobWatcher < Test::Unit::TestCase
|
36
56
|
include EventMachineTailTestHelpers
|
37
57
|
SLEEPMAX = 1
|
@@ -142,5 +162,29 @@ class TestGlobWatcher < Test::Unit::TestCase
|
|
142
162
|
end
|
143
163
|
end
|
144
164
|
end # def test_glob_ignores_file_renames
|
165
|
+
|
166
|
+
def test_glob_finds_modified_files_at_runtime
|
167
|
+
EM.run do
|
168
|
+
abort_after_timeout(SLEEPMAX * @data.length + 10)
|
169
|
+
|
170
|
+
datacopy = @data.clone
|
171
|
+
|
172
|
+
# To test if file edit is detected, file must exist first
|
173
|
+
datacopy.each do |filename|
|
174
|
+
File.new(filename, "w").close
|
175
|
+
end
|
176
|
+
|
177
|
+
EM::watch_glob("#{@dir}/*", ModificationWatcher, @watchinterval, @data.clone, self)
|
178
|
+
|
179
|
+
sleep(2) # Modification time resolution is 1 second, so we need to allow mtimes to change
|
180
|
+
timer = EM::PeriodicTimer.new(0.2) do
|
181
|
+
File.open(datacopy.shift, "w") do |f|
|
182
|
+
f.puts("LOLCAT!")
|
183
|
+
end
|
184
|
+
sleep(rand * SLEEPMAX)
|
185
|
+
timer.cancel if datacopy.length == 0
|
186
|
+
end
|
187
|
+
end # EM.run
|
188
|
+
end # def test_glob_finds_modified_files_at_runtime
|
145
189
|
end # class TestGlobWatcher
|
146
190
|
|
metadata
CHANGED
@@ -1,77 +1,84 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: eventmachine-tail
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
5
|
-
prerelease:
|
4
|
+
version: 0.6.6
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Jordan Sissel
|
9
|
-
autorequire:
|
10
8
|
bindir: bin
|
11
9
|
cert_chain: []
|
12
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
13
11
|
dependencies:
|
14
12
|
- !ruby/object:Gem::Dependency
|
15
13
|
name: eventmachine
|
16
14
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
15
|
requirements:
|
19
|
-
- -
|
16
|
+
- - ">="
|
20
17
|
- !ruby/object:Gem::Version
|
21
18
|
version: '0'
|
22
19
|
type: :runtime
|
23
20
|
prerelease: false
|
24
21
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
22
|
requirements:
|
27
|
-
- -
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: test-unit
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
28
38
|
- !ruby/object:Gem::Version
|
29
39
|
version: '0'
|
30
40
|
description: Add file 'tail' implemented with EventMachine. Also includes a 'glob
|
31
41
|
watch' class for watching a directory pattern for new matches, like /var/log/*.log
|
32
42
|
email: jls@semicomplete.com
|
33
43
|
executables:
|
34
|
-
- rtail
|
35
44
|
- emtail
|
45
|
+
- rtail
|
36
46
|
extensions: []
|
37
47
|
extra_rdoc_files: []
|
38
48
|
files:
|
39
|
-
-
|
40
|
-
-
|
49
|
+
- bin/emtail
|
50
|
+
- bin/rtail
|
41
51
|
- lib/em/filetail.rb
|
42
|
-
-
|
52
|
+
- lib/em/globwatcher.rb
|
53
|
+
- lib/eventmachine-tail.rb
|
54
|
+
- samples/glob-tail.rb
|
43
55
|
- samples/globwatch.rb
|
44
56
|
- samples/tail-with-block.rb
|
45
|
-
- samples/
|
57
|
+
- samples/tail.rb
|
58
|
+
- test/alltests.rb
|
46
59
|
- test/test_filetail.rb
|
47
60
|
- test/test_glob.rb
|
48
61
|
- test/testcase_helpers.rb
|
49
|
-
- test/alltests.rb
|
50
|
-
- bin/emtail
|
51
|
-
- bin/rtail
|
52
62
|
homepage: http://code.google.com/p/semicomplete/wiki/EventMachineTail
|
53
|
-
licenses:
|
54
|
-
|
63
|
+
licenses:
|
64
|
+
- BSD-3-Clause
|
65
|
+
metadata: {}
|
55
66
|
rdoc_options: []
|
56
67
|
require_paths:
|
57
68
|
- lib
|
58
69
|
- lib
|
59
70
|
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
-
none: false
|
61
71
|
requirements:
|
62
|
-
- -
|
72
|
+
- - ">="
|
63
73
|
- !ruby/object:Gem::Version
|
64
74
|
version: '0'
|
65
75
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
-
none: false
|
67
76
|
requirements:
|
68
|
-
- -
|
77
|
+
- - ">="
|
69
78
|
- !ruby/object:Gem::Version
|
70
79
|
version: '0'
|
71
80
|
requirements: []
|
72
|
-
|
73
|
-
|
74
|
-
signing_key:
|
75
|
-
specification_version: 3
|
81
|
+
rubygems_version: 3.6.7
|
82
|
+
specification_version: 4
|
76
83
|
summary: eventmachine tail - a file tail implementation with glob support
|
77
84
|
test_files: []
|