filewatch 0.6.6 → 0.6.7
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/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: []
|