filewatch 0.6.7 → 0.6.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/filewatch/observing_tail.rb +100 -0
- data/lib/filewatch/tail.rb +13 -262
- data/lib/filewatch/tail_base.rb +220 -0
- data/lib/filewatch/watch.rb +121 -33
- data/lib/filewatch/yielding_tail.rb +79 -0
- metadata +17 -19
- data/spec/buftok_spec.rb +0 -18
- data/spec/tail_spec.rb +0 -232
- data/spec/watch_spec.rb +0 -120
- data/spec/winhelper_spec.rb +0 -22
data/lib/filewatch/watch.rb
CHANGED
@@ -5,7 +5,47 @@ end
|
|
5
5
|
|
6
6
|
module FileWatch
|
7
7
|
class Watch
|
8
|
-
|
8
|
+
class WatchedFile
|
9
|
+
def self.new_initial(path, inode)
|
10
|
+
new(path, inode, true)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.new_ongoing(path, inode)
|
14
|
+
new(path, inode, false)
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :size, :inode
|
18
|
+
attr_writer :create_sent, :initial, :timeout_sent
|
19
|
+
|
20
|
+
attr_reader :path
|
21
|
+
|
22
|
+
def initialize(path, inode, initial)
|
23
|
+
@path = path
|
24
|
+
@size, @create_sent, @timeout_sent = 0, false, false
|
25
|
+
@inode, @initial = inode, initial
|
26
|
+
end
|
27
|
+
|
28
|
+
def update(stat, inode = nil)
|
29
|
+
@size = stat.size
|
30
|
+
@inode = inode if inode
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_sent?
|
34
|
+
@create_sent
|
35
|
+
end
|
36
|
+
|
37
|
+
def initial?
|
38
|
+
@initial
|
39
|
+
end
|
40
|
+
|
41
|
+
def timeout_sent?
|
42
|
+
@timeout_sent
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_s() inspect; end
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_accessor :logger, :ignore_after
|
9
49
|
|
10
50
|
public
|
11
51
|
def initialize(opts={})
|
@@ -18,20 +58,18 @@ module FileWatch
|
|
18
58
|
end
|
19
59
|
@watching = []
|
20
60
|
@exclude = []
|
21
|
-
@files = Hash.new { |h, k| h[k] =
|
61
|
+
@files = Hash.new { |h, k| h[k] = WatchedFile.new(k, false, false) }
|
22
62
|
@unwatched = Hash.new
|
23
63
|
# we need to be threadsafe about the mutation
|
24
64
|
# of the above 2 ivars because the public
|
25
65
|
# methods each, discover, watch and unwatch
|
26
66
|
# can be called from different threads.
|
27
67
|
@lock = Mutex.new
|
68
|
+
# we need to be threadsafe about the quit mutation
|
69
|
+
@quit = false
|
70
|
+
@quit_lock = Mutex.new
|
28
71
|
end # def initialize
|
29
72
|
|
30
|
-
public
|
31
|
-
def logger=(logger)
|
32
|
-
@logger = logger
|
33
|
-
end
|
34
|
-
|
35
73
|
public
|
36
74
|
def exclude(path)
|
37
75
|
path.to_a.each { |p| @exclude << p }
|
@@ -42,7 +80,9 @@ module FileWatch
|
|
42
80
|
synchronized do
|
43
81
|
if !@watching.member?(path)
|
44
82
|
@watching << path
|
45
|
-
_discover_file(path,
|
83
|
+
_discover_file(path) do |filepath, stat|
|
84
|
+
WatchedFile.new_initial(filepath, inode(filepath, stat))
|
85
|
+
end
|
46
86
|
end
|
47
87
|
end
|
48
88
|
return true
|
@@ -86,18 +126,18 @@ module FileWatch
|
|
86
126
|
def each(&block)
|
87
127
|
synchronized do
|
88
128
|
# Send any creates.
|
89
|
-
@files.
|
90
|
-
if !
|
91
|
-
if
|
129
|
+
@files.each do |path, watched_file|
|
130
|
+
if !watched_file.create_sent?
|
131
|
+
if watched_file.initial?
|
92
132
|
yield(:create_initial, path)
|
93
133
|
else
|
94
134
|
yield(:create, path)
|
95
135
|
end
|
96
|
-
|
136
|
+
watched_file.create_sent = true
|
97
137
|
end
|
98
138
|
end
|
99
139
|
|
100
|
-
@files.
|
140
|
+
@files.each do |path, watched_file|
|
101
141
|
begin
|
102
142
|
stat = File::Stat.new(path)
|
103
143
|
rescue Errno::ENOENT
|
@@ -108,23 +148,33 @@ module FileWatch
|
|
108
148
|
next
|
109
149
|
end
|
110
150
|
|
151
|
+
if expired?(stat, watched_file)
|
152
|
+
if !watched_file.timeout_sent?
|
153
|
+
@logger.debug? && @logger.debug("#{path}: file expired")
|
154
|
+
yield(:timeout, path)
|
155
|
+
watched_file.timeout_sent = true
|
156
|
+
end
|
157
|
+
next
|
158
|
+
end
|
159
|
+
|
111
160
|
inode = inode(path,stat)
|
112
|
-
|
113
|
-
|
161
|
+
old_size = watched_file.size
|
162
|
+
|
163
|
+
if inode != watched_file.inode
|
164
|
+
@logger.debug? && @logger.debug("#{path}: old inode was #{watched_file.inode.inspect}, new is #{inode.inspect}")
|
114
165
|
yield(:delete, path)
|
115
166
|
yield(:create, path)
|
116
|
-
elsif stat.size <
|
117
|
-
@logger.debug? && @logger.debug("#{path}: file rolled, new size is #{stat.size}, old size #{
|
167
|
+
elsif stat.size < old_size
|
168
|
+
@logger.debug? && @logger.debug("#{path}: file rolled, new size is #{stat.size}, old size #{old_size}")
|
118
169
|
yield(:delete, path)
|
119
170
|
yield(:create, path)
|
120
|
-
elsif stat.size >
|
121
|
-
@logger.debug? && @logger.debug("#{path}: file grew, old size #{
|
171
|
+
elsif stat.size > old_size
|
172
|
+
@logger.debug? && @logger.debug("#{path}: file grew, old size #{old_size}, new size #{stat.size}")
|
122
173
|
yield(:modify, path)
|
123
174
|
end
|
124
175
|
|
125
|
-
|
126
|
-
|
127
|
-
end # @files.keys.each
|
176
|
+
watched_file.update(stat, inode)
|
177
|
+
end
|
128
178
|
end
|
129
179
|
end # def each
|
130
180
|
|
@@ -132,7 +182,9 @@ module FileWatch
|
|
132
182
|
def discover
|
133
183
|
synchronized do
|
134
184
|
@watching.each do |path|
|
135
|
-
_discover_file(path)
|
185
|
+
_discover_file(path) do |filepath, stat|
|
186
|
+
WatchedFile.new_ongoing(filepath, inode(filepath, stat))
|
187
|
+
end
|
136
188
|
end
|
137
189
|
end
|
138
190
|
end
|
@@ -140,8 +192,8 @@ module FileWatch
|
|
140
192
|
public
|
141
193
|
def subscribe(stat_interval = 1, discover_interval = 5, &block)
|
142
194
|
glob = 0
|
143
|
-
|
144
|
-
while
|
195
|
+
reset_quit
|
196
|
+
while !quit?
|
145
197
|
each(&block)
|
146
198
|
|
147
199
|
glob += 1
|
@@ -155,7 +207,24 @@ module FileWatch
|
|
155
207
|
end # def subscribe
|
156
208
|
|
157
209
|
private
|
158
|
-
def
|
210
|
+
def expired?(stat, watched_file)
|
211
|
+
file_expired?(stat) && watched_file.size == stat.size
|
212
|
+
end
|
213
|
+
|
214
|
+
def discover_expired?(stat)
|
215
|
+
file_expired?(stat)
|
216
|
+
end
|
217
|
+
|
218
|
+
def file_expired?(stat)
|
219
|
+
return false unless expiry_enabled?
|
220
|
+
# (Time.now - stat.mtime) <- in jruby, this does int and float
|
221
|
+
# conversions before the subtraction and returns a float.
|
222
|
+
# so use all ints instead
|
223
|
+
(Time.now.to_i - stat.mtime.to_i) > @ignore_after
|
224
|
+
end
|
225
|
+
|
226
|
+
private
|
227
|
+
def _discover_file(path)
|
159
228
|
_globbed_files(path).each do |file|
|
160
229
|
next if @files.member?(file)
|
161
230
|
next if @unwatched.member?(file)
|
@@ -175,15 +244,24 @@ module FileWatch
|
|
175
244
|
next if skip
|
176
245
|
|
177
246
|
stat = File::Stat.new(file)
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
:
|
183
|
-
|
247
|
+
# let the caller build the object in its context
|
248
|
+
watched_file = yield(file, stat)
|
249
|
+
|
250
|
+
if discover_expired?(stat)
|
251
|
+
msg = "_discover_file: #{file}: skipping because it was last modified more than #{@ignore_after} seconds ago"
|
252
|
+
@logger.debug? && @logger.debug(msg)
|
253
|
+
watched_file.update(stat)
|
254
|
+
end
|
255
|
+
|
256
|
+
@files[file] = watched_file
|
184
257
|
end
|
185
258
|
end # def _discover_file
|
186
259
|
|
260
|
+
private
|
261
|
+
def expiry_enabled?
|
262
|
+
!@ignore_after.nil?
|
263
|
+
end
|
264
|
+
|
187
265
|
private
|
188
266
|
def _globbed_files(path)
|
189
267
|
globbed_dirs = Dir.glob(path)
|
@@ -201,9 +279,19 @@ module FileWatch
|
|
201
279
|
@lock.synchronize { block.call }
|
202
280
|
end
|
203
281
|
|
282
|
+
private
|
283
|
+
def quit?
|
284
|
+
@quit_lock.synchronize { @quit }
|
285
|
+
end
|
286
|
+
|
287
|
+
private
|
288
|
+
def reset_quit
|
289
|
+
@quit_lock.synchronize { @quit = false }
|
290
|
+
end
|
291
|
+
|
204
292
|
public
|
205
293
|
def quit
|
206
|
-
@quit = true
|
294
|
+
@quit_lock.synchronize { @quit = true }
|
207
295
|
end # def quit
|
208
296
|
end # class Watch
|
209
297
|
end # module FileWatch
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'filewatch/tail_base'
|
2
|
+
|
3
|
+
module FileWatch
|
4
|
+
class YieldingTail
|
5
|
+
include TailBase
|
6
|
+
|
7
|
+
public
|
8
|
+
def subscribe(&block)
|
9
|
+
# subscribe(stat_interval = 1, discover_interval = 5, &block)
|
10
|
+
@watch.subscribe(@opts[:stat_interval],
|
11
|
+
@opts[:discover_interval]) do |event, path|
|
12
|
+
case event
|
13
|
+
when :create, :create_initial
|
14
|
+
if @files.member?(path)
|
15
|
+
@logger.debug? && @logger.debug("#{event} for #{path}: already exists in @files")
|
16
|
+
next
|
17
|
+
end
|
18
|
+
if _open_file(path, event)
|
19
|
+
yield_read_file(path, &block)
|
20
|
+
end
|
21
|
+
when :modify
|
22
|
+
if !@files.member?(path)
|
23
|
+
@logger.debug? && @logger.debug(":modify for #{path}, does not exist in @files")
|
24
|
+
if _open_file(path, event)
|
25
|
+
yield_read_file(path, &block)
|
26
|
+
end
|
27
|
+
else
|
28
|
+
yield_read_file(path, &block)
|
29
|
+
end
|
30
|
+
when :delete
|
31
|
+
@logger.debug? && @logger.debug(":delete for: #{path} - closed and deleted from @files")
|
32
|
+
if @files[path]
|
33
|
+
yield_read_file(path, &block)
|
34
|
+
@files[path].close
|
35
|
+
end
|
36
|
+
@files.delete(path)
|
37
|
+
@statcache.delete(path)
|
38
|
+
when :timeout
|
39
|
+
@logger.debug? && @logger.debug(":timeout for: #{path} - closed and deleted from @files")
|
40
|
+
if (deleted = @files.delete(path))
|
41
|
+
deleted.close
|
42
|
+
end
|
43
|
+
@statcache.delete(path)
|
44
|
+
else
|
45
|
+
@logger.warn("unknown event type #{event} for #{path}")
|
46
|
+
end
|
47
|
+
end # @watch.subscribe
|
48
|
+
end # def subscribe
|
49
|
+
|
50
|
+
private
|
51
|
+
def yield_read_file(path, &block)
|
52
|
+
@buffers[path] ||= FileWatch::BufferedTokenizer.new(@opts[:delimiter])
|
53
|
+
delimiter_byte_size = @opts[:delimiter].bytesize
|
54
|
+
changed = false
|
55
|
+
loop do
|
56
|
+
begin
|
57
|
+
data = @files[path].sysread(32768)
|
58
|
+
changed = true
|
59
|
+
@buffers[path].extract(data).each do |line|
|
60
|
+
yield(path, line)
|
61
|
+
@sincedb[@statcache[path]] += (line.bytesize + delimiter_byte_size)
|
62
|
+
end
|
63
|
+
rescue Errno::EWOULDBLOCK, Errno::EINTR, EOFError
|
64
|
+
break
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
if changed
|
69
|
+
now = Time.now.to_i
|
70
|
+
delta = now - @sincedb_last_write
|
71
|
+
if delta >= @opts[:sincedb_write_interval]
|
72
|
+
@logger.debug? && @logger.debug("writing sincedb (delta since last write = #{delta})")
|
73
|
+
_sincedb_write
|
74
|
+
@sincedb_last_write = now
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end # module YieldingTail
|
79
|
+
end # module FileWatch
|
metadata
CHANGED
@@ -1,32 +1,31 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: filewatch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jordan Sissel
|
8
8
|
- Pete Fritchman
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-12-
|
12
|
+
date: 2015-12-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name: stud
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
16
|
requirements:
|
18
|
-
- -
|
17
|
+
- - '>='
|
19
18
|
- !ruby/object:Gem::Version
|
20
19
|
version: '0'
|
21
|
-
|
20
|
+
name: stud
|
22
21
|
prerelease: false
|
22
|
+
type: :development
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
|
-
- -
|
25
|
+
- - '>='
|
26
26
|
- !ruby/object:Gem::Version
|
27
27
|
version: '0'
|
28
|
-
description: Watch files and directories in ruby. Also supports tailing and glob file
|
29
|
-
patterns.
|
28
|
+
description: Watch files and directories in ruby. Also supports tailing and glob file patterns.
|
30
29
|
email:
|
31
30
|
- jls@semicomplete.com
|
32
31
|
- petef@databits.net
|
@@ -39,13 +38,12 @@ files:
|
|
39
38
|
- lib/JRubyFileExtension.jar
|
40
39
|
- lib/filewatch/buftok.rb
|
41
40
|
- lib/filewatch/helper.rb
|
41
|
+
- lib/filewatch/observing_tail.rb
|
42
42
|
- lib/filewatch/tail.rb
|
43
|
+
- lib/filewatch/tail_base.rb
|
43
44
|
- lib/filewatch/watch.rb
|
44
45
|
- lib/filewatch/winhelper.rb
|
45
|
-
-
|
46
|
-
- spec/tail_spec.rb
|
47
|
-
- spec/watch_spec.rb
|
48
|
-
- spec/winhelper_spec.rb
|
46
|
+
- lib/filewatch/yielding_tail.rb
|
49
47
|
- test/filewatch/tail.rb
|
50
48
|
- test/globtail/Makefile
|
51
49
|
- test/globtail/framework.sh
|
@@ -72,25 +70,25 @@ files:
|
|
72
70
|
homepage: https://github.com/jordansissel/ruby-filewatch
|
73
71
|
licenses: []
|
74
72
|
metadata: {}
|
75
|
-
post_install_message:
|
73
|
+
post_install_message:
|
76
74
|
rdoc_options: []
|
77
75
|
require_paths:
|
78
76
|
- lib
|
79
77
|
- lib
|
80
78
|
required_ruby_version: !ruby/object:Gem::Requirement
|
81
79
|
requirements:
|
82
|
-
- -
|
80
|
+
- - '>='
|
83
81
|
- !ruby/object:Gem::Version
|
84
82
|
version: '0'
|
85
83
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
84
|
requirements:
|
87
|
-
- -
|
85
|
+
- - '>='
|
88
86
|
- !ruby/object:Gem::Version
|
89
87
|
version: '0'
|
90
88
|
requirements: []
|
91
|
-
rubyforge_project:
|
92
|
-
rubygems_version: 2.4.
|
93
|
-
signing_key:
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 2.4.8
|
91
|
+
signing_key:
|
94
92
|
specification_version: 4
|
95
93
|
summary: filewatch - file watching for ruby
|
96
94
|
test_files: []
|
data/spec/buftok_spec.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
require 'filewatch/buftok'
|
2
|
-
|
3
|
-
describe FileWatch::BufferedTokenizer do
|
4
|
-
|
5
|
-
context "when using the default delimiter" do
|
6
|
-
it "splits the lines correctly" do
|
7
|
-
expect(subject.extract("hello\nworld\n")).to eq ["hello", "world"]
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
context "when passing a custom delimiter" do
|
12
|
-
subject { FileWatch::BufferedTokenizer.new("\r\n") }
|
13
|
-
|
14
|
-
it "splits the lines correctly" do
|
15
|
-
expect(subject.extract("hello\r\nworld\r\n")).to eq ["hello", "world"]
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
data/spec/tail_spec.rb
DELETED
@@ -1,232 +0,0 @@
|
|
1
|
-
require 'filewatch/tail'
|
2
|
-
require 'stud/temporary'
|
3
|
-
require "rbconfig"
|
4
|
-
|
5
|
-
describe FileWatch::Tail do
|
6
|
-
before(:all) do
|
7
|
-
@thread_abort = Thread.abort_on_exception
|
8
|
-
Thread.abort_on_exception = true
|
9
|
-
end
|
10
|
-
|
11
|
-
after(:all) do
|
12
|
-
Thread.abort_on_exception = @thread_abort
|
13
|
-
end
|
14
|
-
|
15
|
-
let(:file_path) { f = Stud::Temporary.pathname }
|
16
|
-
let(:sincedb_path) { Stud::Temporary.pathname }
|
17
|
-
|
18
|
-
before :each do
|
19
|
-
Thread.new(subject) { sleep 0.5; subject.quit } # force the subscribe loop to exit
|
20
|
-
end
|
21
|
-
|
22
|
-
context "when watching a new file" do
|
23
|
-
subject { FileWatch::Tail.new(:sincedb_path => sincedb_path, :start_new_files_at => :beginning, :stat_interval => 0) }
|
24
|
-
|
25
|
-
before :each do
|
26
|
-
subject.tail(file_path)
|
27
|
-
File.open(file_path, "wb") { |file| file.write("line1\nline2\n") }
|
28
|
-
end
|
29
|
-
|
30
|
-
it "reads new lines off the file" do
|
31
|
-
expect { |b| subject.subscribe(&b) }.to yield_successive_args([file_path, "line1"], [file_path, "line2"])
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
context "when watching a file" do
|
36
|
-
subject { FileWatch::Tail.new(:sincedb_path => sincedb_path, :start_new_files_at => :beginning, :stat_interval => 0) }
|
37
|
-
|
38
|
-
before :each do
|
39
|
-
File.open(file_path, "wb") { |file| file.write("line1\nline2\n") }
|
40
|
-
subject.tail(file_path)
|
41
|
-
end
|
42
|
-
|
43
|
-
it "reads new lines off the file" do
|
44
|
-
expect { |b| subject.subscribe(&b) }.to yield_successive_args([file_path, "line1"], [file_path, "line2"])
|
45
|
-
end
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
context "when watching a CRLF file" do
|
50
|
-
subject { FileWatch::Tail.new(:sincedb_path => sincedb_path,
|
51
|
-
:start_new_files_at => :beginning,
|
52
|
-
:delimiter => "\r\n") }
|
53
|
-
|
54
|
-
before :each do
|
55
|
-
File.open(file_path, "wb") { |file| file.write("line1\r\nline2\r\n") }
|
56
|
-
subject.tail(file_path)
|
57
|
-
end
|
58
|
-
|
59
|
-
it "reads new lines off the file" do
|
60
|
-
expect { |b| subject.subscribe(&b) }.to yield_successive_args([file_path, "line1"], [file_path, "line2"])
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
context "when a file is deleted" do
|
65
|
-
subject { FileWatch::Tail.new(:sincedb_path => sincedb_path, :start_new_files_at => :beginning) }
|
66
|
-
|
67
|
-
before :each do
|
68
|
-
File.open(file_path, "w") { |file| file.write("line1\nline2\n") }
|
69
|
-
subject.tail(file_path)
|
70
|
-
File.unlink file_path
|
71
|
-
end
|
72
|
-
|
73
|
-
it "should not raise exception" do
|
74
|
-
Thread.new(subject) { sleep 0.1; subject.quit } # force the subscribe loop to exit
|
75
|
-
expect { subject.subscribe {|p,l| } }.to_not raise_exception
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
describe "sincedb" do
|
80
|
-
subject { FileWatch::Tail.new(:sincedb_path => sincedb_path, :start_new_files_at => :beginning, :stat_interval => 0) }
|
81
|
-
|
82
|
-
before :each do
|
83
|
-
File.open(file_path, "wb") { |file| file.write("line1\nline2\n") }
|
84
|
-
subject.tail(file_path)
|
85
|
-
end
|
86
|
-
|
87
|
-
context "when reading a new file" do
|
88
|
-
it "updates sincedb after subscribe" do
|
89
|
-
subject.subscribe {|_,_| }
|
90
|
-
stat = File::Stat.new(file_path)
|
91
|
-
sincedb_id = subject.sincedb_record_uid(file_path,stat).join(' ')
|
92
|
-
expect(File.read(sincedb_path)).to eq("#{sincedb_id} #{stat.size}\n")
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
context "when restarting tail" do
|
97
|
-
before :each do
|
98
|
-
subject.subscribe {|_,_| }
|
99
|
-
sleep 0.6 # wait for tail.quit
|
100
|
-
subject.tail(file_path) # re-tail file
|
101
|
-
File.open(file_path, "ab") { |file| file.write("line3\nline4\n") }
|
102
|
-
Thread.new(subject) { sleep 0.5; subject.quit }
|
103
|
-
end
|
104
|
-
|
105
|
-
it "picks off from where it stopped" do
|
106
|
-
expect { |b| subject.subscribe(&b) }.to yield_successive_args([file_path, "line3"], [file_path, "line4"])
|
107
|
-
end
|
108
|
-
|
109
|
-
it "updates on tail.quit" do
|
110
|
-
subject.subscribe {|_,_| }
|
111
|
-
stat = File::Stat.new(file_path)
|
112
|
-
sincedb_id = subject.sincedb_record_uid(file_path,stat).join(' ')
|
113
|
-
expect(File.read(sincedb_path)).to eq("#{sincedb_id} #{stat.size}\n")
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
context "ingesting files bigger than 32k" do
|
119
|
-
let(:lineA) { "a" * 12000 }
|
120
|
-
let(:lineB) { "b" * 25000 }
|
121
|
-
let(:lineC) { "c" * 8000 }
|
122
|
-
subject { FileWatch::Tail.new(:sincedb_path => sincedb_path, :start_new_files_at => :beginning) }
|
123
|
-
|
124
|
-
before :each do
|
125
|
-
IO.write(file_path, "#{lineA}\n#{lineB}\n#{lineC}\n")
|
126
|
-
end
|
127
|
-
|
128
|
-
context "when restarting after stopping at the first line" do
|
129
|
-
|
130
|
-
let(:new_subject) { FileWatch::Tail.new(:sincedb_path => sincedb_path, :start_new_files_at => :beginning) }
|
131
|
-
|
132
|
-
before :each do
|
133
|
-
subject.tail(file_path)
|
134
|
-
subject.subscribe {|f, l| break if @test; @test = 1}
|
135
|
-
subject.sincedb_write
|
136
|
-
subject.quit
|
137
|
-
Thread.new(new_subject) { sleep 0.5; new_subject.quit } # force the subscribe loop to exit
|
138
|
-
end
|
139
|
-
|
140
|
-
it "should store in sincedb the position up until the first string" do
|
141
|
-
device, dev_major, dev_minor, pos = *IO.read(sincedb_path).split(" ").map {|n| n.to_i }
|
142
|
-
expect(pos).to eq(12001) # string.bytesize + "\n".bytesize
|
143
|
-
end
|
144
|
-
|
145
|
-
it "should read the second and third lines entirely" do
|
146
|
-
new_subject.tail(file_path) # re-tail file
|
147
|
-
expect { |b| new_subject.subscribe(&b) }.to yield_successive_args([file_path, lineB], [file_path, lineC])
|
148
|
-
end
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
context "when watching a directory" do
|
153
|
-
|
154
|
-
let(:directory) { Stud::Temporary.directory }
|
155
|
-
let(:file_path) { File.join(directory, "1.log") }
|
156
|
-
|
157
|
-
subject { FileWatch::Tail.new(:sincedb_path => sincedb_path, :start_new_files_at => :beginning, :stat_interval => 0) }
|
158
|
-
|
159
|
-
before :each do
|
160
|
-
File.open(file_path, "wb") { |file| file.write("line1\nline2\n") }
|
161
|
-
subject.tail(File.join(directory, "*"))
|
162
|
-
end
|
163
|
-
|
164
|
-
after :each do
|
165
|
-
FileUtils.rm_rf(directory)
|
166
|
-
end
|
167
|
-
|
168
|
-
it "reads new lines from the beginning" do
|
169
|
-
expect { |b| subject.subscribe(&b) }.to yield_successive_args([file_path, "line1"], [file_path, "line2"])
|
170
|
-
end
|
171
|
-
|
172
|
-
context "when a file is renamed" do
|
173
|
-
|
174
|
-
before :each do
|
175
|
-
expect { |b| subject.subscribe(&b) }.to yield_successive_args([file_path, "line1"], [file_path, "line2"])
|
176
|
-
File.rename(file_path, file_path + ".bak")
|
177
|
-
end
|
178
|
-
|
179
|
-
it "should not re-read the file" do
|
180
|
-
Thread.new(subject) { |s| sleep 1; s.quit }
|
181
|
-
expect { |b| subject.subscribe(&b) }.not_to yield_control
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
let(:new_file_path) { File.join(directory, "2.log") }
|
186
|
-
|
187
|
-
context "when a new file is later added to the directory" do
|
188
|
-
# Note tests in this context rely on FileWatch::Watch reading
|
189
|
-
# file 1.log first then 2.log and that depends on how Dir.glob is implemented
|
190
|
-
# in different rubies on different operating systems
|
191
|
-
before do
|
192
|
-
File.open(new_file_path, "wb") { |file| file.write("line2.1\nline2.2\n") }
|
193
|
-
end
|
194
|
-
|
195
|
-
it "reads new lines from the beginning for all files" do
|
196
|
-
expect { |b| subject.subscribe(&b) }.to yield_successive_args([file_path, "line1"], [file_path, "line2"],
|
197
|
-
[new_file_path, "line2.1"], [new_file_path, "line2.2"])
|
198
|
-
end
|
199
|
-
|
200
|
-
context "and when the sincedb path is not given" do
|
201
|
-
subject { FileWatch::Tail.new(:start_new_files_at => :beginning, :stat_interval => 0) }
|
202
|
-
|
203
|
-
it "reads new lines from the beginning for all files" do
|
204
|
-
expect { |b| subject.subscribe(&b) }.to yield_successive_args([file_path, "line1"], [file_path, "line2"],
|
205
|
-
[new_file_path, "line2.1"], [new_file_path, "line2.2"])
|
206
|
-
end
|
207
|
-
end
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
if RbConfig::CONFIG['host_os'] !~ /mswin|mingw|cygwin/
|
212
|
-
context "when quiting" do
|
213
|
-
subject { FileWatch::Tail.new(:sincedb_path => sincedb_path, :start_new_files_at => :beginning, :stat_interval => 0) }
|
214
|
-
|
215
|
-
before :each do
|
216
|
-
subject.tail(file_path)
|
217
|
-
File.open(file_path, "wb") { |file| file.write("line1\nline2\n") }
|
218
|
-
end
|
219
|
-
|
220
|
-
it "closes the file handles" do
|
221
|
-
buffer = []
|
222
|
-
subject.subscribe do |path, line|
|
223
|
-
buffer.push([path, line])
|
224
|
-
end
|
225
|
-
subject.sincedb_write
|
226
|
-
subject.quit
|
227
|
-
lsof = `lsof -p #{Process.pid} | grep #{file_path}`
|
228
|
-
expect(lsof).to be_empty
|
229
|
-
end
|
230
|
-
end
|
231
|
-
end
|
232
|
-
end
|