filewatch 0.6.6 → 0.6.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/filewatch/tail.rb +22 -2
- data/lib/filewatch/watch.rb +89 -49
- data/spec/buftok_spec.rb +18 -0
- data/spec/tail_spec.rb +232 -0
- data/spec/watch_spec.rb +120 -0
- data/spec/winhelper_spec.rb +22 -0
- metadata +19 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3c7e0e544aadf722674dd85b5ba0441dce6ecfc4
|
4
|
+
data.tar.gz: 4f6d362df8d9d5f105bcbf12f576e0cf8db69966
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5fa00a10ea8b093e6484aabbd9f653a7d2f06b97dafdcf2a2f2795f750cf2c3f0d8c13b19f2c41809e5284ed44316e7b448eda1499f7c9b817309a53a34d33c9
|
7
|
+
data.tar.gz: 0c8f460f91f9830deb1b096c22facc1106ea99a73ec297ad41384787bfa29f5302760696411d6b2c3517bb9b3eaa226343a0895ac78eb298ee1a04309b16a51a
|
data/lib/filewatch/tail.rb
CHANGED
@@ -155,7 +155,7 @@ module FileWatch
|
|
155
155
|
@logger.debug? && @logger.debug("#{path}: initial create, no sincedb, seeking to beginning of file")
|
156
156
|
@files[path].sysseek(0, IO::SEEK_SET)
|
157
157
|
@sincedb[sincedb_record_uid] = 0
|
158
|
-
else
|
158
|
+
else
|
159
159
|
# seek to end
|
160
160
|
@logger.debug? && @logger.debug("#{path}: initial create, no sincedb, seeking to end #{stat.size}")
|
161
161
|
@files[path].sysseek(stat.size, IO::SEEK_SET)
|
@@ -229,7 +229,7 @@ module FileWatch
|
|
229
229
|
private
|
230
230
|
def _sincedb_write
|
231
231
|
path = @opts[:sincedb_path]
|
232
|
-
if File.device?(path)
|
232
|
+
if @iswindows || File.device?(path)
|
233
233
|
IO.write(path, serialize_sincedb, 0)
|
234
234
|
else
|
235
235
|
File.atomic_write(path) {|file| file.write(serialize_sincedb) }
|
@@ -237,11 +237,31 @@ module FileWatch
|
|
237
237
|
end # def _sincedb_write
|
238
238
|
|
239
239
|
public
|
240
|
+
# quit is a sort-of finalizer,
|
241
|
+
# it should be called for clean up
|
242
|
+
# before the instance is disposed of.
|
240
243
|
def quit
|
241
244
|
_sincedb_write
|
242
245
|
@watch.quit
|
246
|
+
@files.each {|path, file| file.close }
|
247
|
+
@files.clear
|
243
248
|
end # def quit
|
244
249
|
|
250
|
+
public
|
251
|
+
# close_file(path) is to be used by external code
|
252
|
+
# when it knows that it is completely done with a file.
|
253
|
+
# Other files or folders may still be being watched.
|
254
|
+
# Caution, once unwatched, a file can't be watched again
|
255
|
+
# unless a new instance of this class begins watching again.
|
256
|
+
# The sysadmin should rename, move or delete the file.
|
257
|
+
def close_file(path)
|
258
|
+
@watch.unwatch(path)
|
259
|
+
file = @files.delete(path)
|
260
|
+
return if file.nil?
|
261
|
+
_sincedb_write
|
262
|
+
file.close
|
263
|
+
end
|
264
|
+
|
245
265
|
private
|
246
266
|
def serialize_sincedb
|
247
267
|
@sincedb.map do |inode, pos|
|
data/lib/filewatch/watch.rb
CHANGED
@@ -19,6 +19,12 @@ module FileWatch
|
|
19
19
|
@watching = []
|
20
20
|
@exclude = []
|
21
21
|
@files = Hash.new { |h, k| h[k] = Hash.new }
|
22
|
+
@unwatched = Hash.new
|
23
|
+
# we need to be threadsafe about the mutation
|
24
|
+
# of the above 2 ivars because the public
|
25
|
+
# methods each, discover, watch and unwatch
|
26
|
+
# can be called from different threads.
|
27
|
+
@lock = Mutex.new
|
22
28
|
end # def initialize
|
23
29
|
|
24
30
|
public
|
@@ -33,19 +39,37 @@ module FileWatch
|
|
33
39
|
|
34
40
|
public
|
35
41
|
def watch(path)
|
36
|
-
|
37
|
-
|
38
|
-
|
42
|
+
synchronized do
|
43
|
+
if !@watching.member?(path)
|
44
|
+
@watching << path
|
45
|
+
_discover_file(path, true)
|
46
|
+
end
|
39
47
|
end
|
40
|
-
|
41
48
|
return true
|
42
49
|
end # def watch
|
43
50
|
|
51
|
+
def unwatch(path)
|
52
|
+
synchronized do
|
53
|
+
result = false
|
54
|
+
if @watching.delete(path)
|
55
|
+
_globbed_files(path).each do |file|
|
56
|
+
deleted = @files.delete(file)
|
57
|
+
@unwatched[file] = deleted if deleted
|
58
|
+
end
|
59
|
+
result = true
|
60
|
+
else
|
61
|
+
result = @files.delete(path)
|
62
|
+
@unwatched[path] = result if result
|
63
|
+
end
|
64
|
+
return !!result
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
44
68
|
public
|
45
69
|
def inode(path,stat)
|
46
70
|
if @iswindows
|
47
71
|
fileId = Winhelper.GetWindowsUniqueFileIdentifier(path)
|
48
|
-
inode = [fileId,
|
72
|
+
inode = [fileId, 0, 0] # dev_* doesn't make sense on Windows
|
49
73
|
else
|
50
74
|
inode = [stat.ino.to_s, stat.dev_major, stat.dev_minor]
|
51
75
|
end
|
@@ -60,52 +84,56 @@ module FileWatch
|
|
60
84
|
# :delete - file is deleted
|
61
85
|
public
|
62
86
|
def each(&block)
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
if @files[path][:
|
67
|
-
|
68
|
-
|
69
|
-
|
87
|
+
synchronized do
|
88
|
+
# Send any creates.
|
89
|
+
@files.keys.each do |path|
|
90
|
+
if ! @files[path][:create_sent]
|
91
|
+
if @files[path][:initial]
|
92
|
+
yield(:create_initial, path)
|
93
|
+
else
|
94
|
+
yield(:create, path)
|
95
|
+
end
|
96
|
+
@files[path][:create_sent] = true
|
70
97
|
end
|
71
|
-
@files[path][:create_sent] = true
|
72
98
|
end
|
73
|
-
end
|
74
99
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
100
|
+
@files.keys.each do |path|
|
101
|
+
begin
|
102
|
+
stat = File::Stat.new(path)
|
103
|
+
rescue Errno::ENOENT
|
104
|
+
# file has gone away or we can't read it anymore.
|
105
|
+
@files.delete(path)
|
106
|
+
@logger.debug? && @logger.debug("#{path}: stat failed (#{$!}), deleting from @files")
|
107
|
+
yield(:delete, path)
|
108
|
+
next
|
109
|
+
end
|
85
110
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
111
|
+
inode = inode(path,stat)
|
112
|
+
if inode != @files[path][:inode]
|
113
|
+
@logger.debug? && @logger.debug("#{path}: old inode was #{@files[path][:inode].inspect}, new is #{inode.inspect}")
|
114
|
+
yield(:delete, path)
|
115
|
+
yield(:create, path)
|
116
|
+
elsif stat.size < @files[path][:size]
|
117
|
+
@logger.debug? && @logger.debug("#{path}: file rolled, new size is #{stat.size}, old size #{@files[path][:size]}")
|
118
|
+
yield(:delete, path)
|
119
|
+
yield(:create, path)
|
120
|
+
elsif stat.size > @files[path][:size]
|
121
|
+
@logger.debug? && @logger.debug("#{path}: file grew, old size #{@files[path][:size]}, new size #{stat.size}")
|
122
|
+
yield(:modify, path)
|
123
|
+
end
|
99
124
|
|
100
|
-
|
101
|
-
|
102
|
-
|
125
|
+
@files[path][:size] = stat.size
|
126
|
+
@files[path][:inode] = inode
|
127
|
+
end # @files.keys.each
|
128
|
+
end
|
103
129
|
end # def each
|
104
130
|
|
105
131
|
public
|
106
132
|
def discover
|
107
|
-
|
108
|
-
|
133
|
+
synchronized do
|
134
|
+
@watching.each do |path|
|
135
|
+
_discover_file(path)
|
136
|
+
end
|
109
137
|
end
|
110
138
|
end
|
111
139
|
|
@@ -128,14 +156,9 @@ module FileWatch
|
|
128
156
|
|
129
157
|
private
|
130
158
|
def _discover_file(path, initial=false)
|
131
|
-
|
132
|
-
@logger.debug? && @logger.debug("_discover_file_glob: #{path}: glob is: #{globbed_dirs}")
|
133
|
-
if globbed_dirs.empty? && File.file?(path)
|
134
|
-
globbed_dirs = [path]
|
135
|
-
@logger.debug? && @logger.debug("_discover_file_glob: #{path}: glob is: #{globbed_dirs} because glob did not work")
|
136
|
-
end
|
137
|
-
globbed_dirs.each do |file|
|
159
|
+
_globbed_files(path).each do |file|
|
138
160
|
next if @files.member?(file)
|
161
|
+
next if @unwatched.member?(file)
|
139
162
|
next unless File.file?(file)
|
140
163
|
|
141
164
|
@logger.debug? && @logger.debug("_discover_file: #{path}: new: #{file} (exclude is #{@exclude.inspect})")
|
@@ -161,6 +184,23 @@ module FileWatch
|
|
161
184
|
end
|
162
185
|
end # def _discover_file
|
163
186
|
|
187
|
+
private
|
188
|
+
def _globbed_files(path)
|
189
|
+
globbed_dirs = Dir.glob(path)
|
190
|
+
@logger.debug? && @logger.debug("_globbed_files: #{path}: glob is: #{globbed_dirs}")
|
191
|
+
if globbed_dirs.empty? && File.file?(path)
|
192
|
+
globbed_dirs = [path]
|
193
|
+
@logger.debug? && @logger.debug("_globbed_files: #{path}: glob is: #{globbed_dirs} because glob did not work")
|
194
|
+
end
|
195
|
+
# return Enumerator
|
196
|
+
globbed_dirs.to_enum
|
197
|
+
end
|
198
|
+
|
199
|
+
private
|
200
|
+
def synchronized(&block)
|
201
|
+
@lock.synchronize { block.call }
|
202
|
+
end
|
203
|
+
|
164
204
|
public
|
165
205
|
def quit
|
166
206
|
@quit = true
|
data/spec/buftok_spec.rb
ADDED
@@ -0,0 +1,18 @@
|
|
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
ADDED
@@ -0,0 +1,232 @@
|
|
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
|
data/spec/watch_spec.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'filewatch/watch'
|
2
|
+
require 'stud/temporary'
|
3
|
+
|
4
|
+
describe FileWatch::Watch do
|
5
|
+
before(:all) do
|
6
|
+
@thread_abort = Thread.abort_on_exception
|
7
|
+
Thread.abort_on_exception = true
|
8
|
+
end
|
9
|
+
|
10
|
+
after(:all) do
|
11
|
+
Thread.abort_on_exception = @thread_abort
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:directory) { Stud::Temporary.directory }
|
15
|
+
let(:file_path) { File.join(directory, "1.log") }
|
16
|
+
let(:loggr) { double("loggr", :debug? => true) }
|
17
|
+
let(:results) { [] }
|
18
|
+
let(:quit_proc) do
|
19
|
+
lambda do
|
20
|
+
Thread.new do
|
21
|
+
sleep 1
|
22
|
+
subject.quit
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
let(:subscribe_proc) do
|
27
|
+
lambda do
|
28
|
+
subject.subscribe(0.1, 4) do |event, path|
|
29
|
+
results.push([event, path])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
let(:write_lines_1_and_2_proc) do
|
34
|
+
lambda do
|
35
|
+
File.open(file_path, "wb") { |file| file.write("line1\nline2\n") }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
subject { FileWatch::Watch.new(:logger => loggr) }
|
40
|
+
|
41
|
+
before do
|
42
|
+
allow(loggr).to receive(:debug)
|
43
|
+
end
|
44
|
+
|
45
|
+
after do
|
46
|
+
FileUtils.rm_rf(directory)
|
47
|
+
end
|
48
|
+
|
49
|
+
context "when watching a directory with files" do
|
50
|
+
it "yields create_initial and one modify file events" do
|
51
|
+
write_lines_1_and_2_proc.call
|
52
|
+
subject.watch(File.join(directory, "*"))
|
53
|
+
quit_proc.call
|
54
|
+
subscribe_proc.call
|
55
|
+
expect(results).to eq([[:create_initial, file_path], [:modify, file_path]])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "when watching a directory without files and one is added" do
|
60
|
+
it "yields create and one modify file events" do
|
61
|
+
subject.watch(File.join(directory, "*"))
|
62
|
+
write_lines_1_and_2_proc.call
|
63
|
+
|
64
|
+
quit_proc.call
|
65
|
+
subscribe_proc.call
|
66
|
+
|
67
|
+
expect(results).to eq([[:create, file_path], [:modify, file_path]])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "when watching a directory with files and data is appended" do
|
72
|
+
let(:write_lines_3_and_4_proc) do
|
73
|
+
lambda do
|
74
|
+
Thread.new do
|
75
|
+
sleep 0.5
|
76
|
+
File.open(file_path, "ab") { |file| file.write("line3\nline4\n") }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it "yields create_initial and two modified file events" do
|
82
|
+
write_lines_1_and_2_proc.call
|
83
|
+
subject.watch(File.join(directory, "*"))
|
84
|
+
|
85
|
+
write_lines_3_and_4_proc.call # asynchronous
|
86
|
+
|
87
|
+
quit_proc.call
|
88
|
+
subscribe_proc.call
|
89
|
+
|
90
|
+
expect(results).to eq([[:create_initial, file_path], [:modify, file_path], [:modify, file_path]])
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "when unwatching a file and data is appended" do
|
95
|
+
let(:write_lines_3_and_4_proc) do
|
96
|
+
lambda do
|
97
|
+
Thread.new do
|
98
|
+
sleep 0.2
|
99
|
+
results.clear
|
100
|
+
subject.unwatch(file_path)
|
101
|
+
sleep 0.2
|
102
|
+
File.open(file_path, "ab") { |file| file.write("line3\nline4\n") }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
it "does not yield events after unwatching" do
|
108
|
+
write_lines_1_and_2_proc.call
|
109
|
+
subject.watch(File.join(directory, "*"))
|
110
|
+
|
111
|
+
write_lines_3_and_4_proc.call # asynchronous
|
112
|
+
|
113
|
+
quit_proc.call
|
114
|
+
subscribe_proc.call
|
115
|
+
|
116
|
+
expect(results).to eq([])
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "stud/temporary"
|
2
|
+
require "fileutils"
|
3
|
+
|
4
|
+
if Gem.win_platform?
|
5
|
+
require "lib/filewatch/winhelper"
|
6
|
+
|
7
|
+
describe Winhelper do
|
8
|
+
let(:path) { Stud::Temporary.file.path }
|
9
|
+
|
10
|
+
after do
|
11
|
+
FileUtils.rm_rf(path)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "return a unique file identifier" do
|
15
|
+
volume_serial, file_index_low, file_index_high = Winhelper.GetWindowsUniqueFileIdentifier(path).split("").map(&:to_i)
|
16
|
+
|
17
|
+
expect(volume_serial).not_to eq(0)
|
18
|
+
expect(file_index_low).not_to eq(0)
|
19
|
+
expect(file_index_high).not_to eq(0)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
metadata
CHANGED
@@ -1,31 +1,32 @@
|
|
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.7
|
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
|
+
date: 2015-12-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
+
name: stud
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
16
17
|
requirements:
|
17
|
-
- -
|
18
|
+
- - ">="
|
18
19
|
- !ruby/object:Gem::Version
|
19
20
|
version: '0'
|
20
|
-
name: stud
|
21
|
-
prerelease: false
|
22
21
|
type: :development
|
22
|
+
prerelease: false
|
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
|
28
|
+
description: Watch files and directories in ruby. Also supports tailing and glob file
|
29
|
+
patterns.
|
29
30
|
email:
|
30
31
|
- jls@semicomplete.com
|
31
32
|
- petef@databits.net
|
@@ -41,6 +42,10 @@ files:
|
|
41
42
|
- lib/filewatch/tail.rb
|
42
43
|
- lib/filewatch/watch.rb
|
43
44
|
- lib/filewatch/winhelper.rb
|
45
|
+
- spec/buftok_spec.rb
|
46
|
+
- spec/tail_spec.rb
|
47
|
+
- spec/watch_spec.rb
|
48
|
+
- spec/winhelper_spec.rb
|
44
49
|
- test/filewatch/tail.rb
|
45
50
|
- test/globtail/Makefile
|
46
51
|
- test/globtail/framework.sh
|
@@ -67,25 +72,25 @@ files:
|
|
67
72
|
homepage: https://github.com/jordansissel/ruby-filewatch
|
68
73
|
licenses: []
|
69
74
|
metadata: {}
|
70
|
-
post_install_message:
|
75
|
+
post_install_message:
|
71
76
|
rdoc_options: []
|
72
77
|
require_paths:
|
73
78
|
- lib
|
74
79
|
- lib
|
75
80
|
required_ruby_version: !ruby/object:Gem::Requirement
|
76
81
|
requirements:
|
77
|
-
- -
|
82
|
+
- - ">="
|
78
83
|
- !ruby/object:Gem::Version
|
79
84
|
version: '0'
|
80
85
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
86
|
requirements:
|
82
|
-
- -
|
87
|
+
- - ">="
|
83
88
|
- !ruby/object:Gem::Version
|
84
89
|
version: '0'
|
85
90
|
requirements: []
|
86
|
-
rubyforge_project:
|
87
|
-
rubygems_version: 2.4.
|
88
|
-
signing_key:
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 2.4.6
|
93
|
+
signing_key:
|
89
94
|
specification_version: 4
|
90
95
|
summary: filewatch - file watching for ruby
|
91
96
|
test_files: []
|