filewatch 0.6.7 → 0.6.8
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 +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
|