filewatch 0.7.1 → 0.8.0
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 +35 -28
- data/lib/filewatch/tail_base.rb +81 -65
- data/lib/filewatch/watch.rb +198 -168
- data/lib/filewatch/watched_file.rb +173 -0
- data/lib/filewatch/yielding_tail.rb +31 -28
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 59ac6ba9efca68da9ea0b3342e87deab3635f53c
|
4
|
+
data.tar.gz: 5a71f0673de75e1444584b01c0dcfe56e039d2ce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 366003c080654805fa4da59a6ef354a25cb0a8495090d668ad0dd8ae628e7bf7438b5d0894938925bed8d8e2b08a6dae3671a0772480d94d210039273aaa7e01
|
7
|
+
data.tar.gz: d05f46ef57830653fbd19c3eef0d4e49fd4137f5604d0296df1991fc64ae0b6b74c926a7e2796bf528d4c55d1571f8a9b190115b88571110027bb16ebe6fe8fb
|
@@ -21,43 +21,45 @@ module FileWatch
|
|
21
21
|
|
22
22
|
def subscribe(observer = NullObserver.new)
|
23
23
|
@watch.subscribe(@opts[:stat_interval],
|
24
|
-
@opts[:discover_interval]) do |event,
|
24
|
+
@opts[:discover_interval]) do |event, watched_file|
|
25
|
+
path = watched_file.path
|
26
|
+
file_is_open = watched_file.file_open?
|
25
27
|
listener = observer.listener_for(path)
|
26
28
|
case event
|
29
|
+
when :unignore
|
30
|
+
listener.created
|
31
|
+
_add_to_sincedb(watched_file, event) unless @sincedb.member?(watched_file.inode)
|
27
32
|
when :create, :create_initial
|
28
|
-
if
|
29
|
-
@logger.debug? && @logger.debug("#{event} for #{path}: already
|
33
|
+
if file_is_open
|
34
|
+
@logger.debug? && @logger.debug("#{event} for #{path}: file already open")
|
30
35
|
next
|
31
36
|
end
|
32
|
-
if _open_file(
|
37
|
+
if _open_file(watched_file, event)
|
33
38
|
listener.created
|
34
|
-
observe_read_file(
|
39
|
+
observe_read_file(watched_file, listener)
|
35
40
|
end
|
36
41
|
when :modify
|
37
|
-
if
|
38
|
-
|
39
|
-
if _open_file(path, event)
|
40
|
-
observe_read_file(path, listener)
|
41
|
-
end
|
42
|
+
if file_is_open
|
43
|
+
observe_read_file(watched_file, listener)
|
42
44
|
else
|
43
|
-
|
45
|
+
@logger.debug? && @logger.debug(":modify for #{path}, file is not open, opening now")
|
46
|
+
if _open_file(watched_file, event)
|
47
|
+
observe_read_file(watched_file, listener)
|
48
|
+
end
|
44
49
|
end
|
45
50
|
when :delete
|
46
|
-
|
47
|
-
|
48
|
-
observe_read_file(
|
49
|
-
|
51
|
+
if file_is_open
|
52
|
+
@logger.debug? && @logger.debug(":delete for #{path}, closing file")
|
53
|
+
observe_read_file(watched_file, listener)
|
54
|
+
watched_file.file_close
|
55
|
+
else
|
56
|
+
@logger.debug? && @logger.debug(":delete for #{path}, file already closed")
|
50
57
|
end
|
51
58
|
listener.deleted
|
52
|
-
@files.delete(path)
|
53
|
-
@statcache.delete(path)
|
54
59
|
when :timeout
|
55
|
-
@logger.debug? && @logger.debug(":timeout for #{path},
|
56
|
-
|
57
|
-
deleted.close
|
58
|
-
end
|
60
|
+
@logger.debug? && @logger.debug(":timeout for #{path}, closing file")
|
61
|
+
watched_file.file_close
|
59
62
|
listener.timed_out
|
60
|
-
@statcache.delete(path)
|
61
63
|
else
|
62
64
|
@logger.warn("unknown event type #{event} for #{path}")
|
63
65
|
end
|
@@ -65,24 +67,29 @@ module FileWatch
|
|
65
67
|
end # def subscribe
|
66
68
|
|
67
69
|
private
|
68
|
-
def observe_read_file(
|
69
|
-
@buffers[path] ||= FileWatch::BufferedTokenizer.new(@opts[:delimiter])
|
70
|
-
delimiter_byte_size = @opts[:delimiter].bytesize
|
70
|
+
def observe_read_file(watched_file, listener)
|
71
71
|
changed = false
|
72
72
|
loop do
|
73
73
|
begin
|
74
|
-
data =
|
74
|
+
data = watched_file.file_read(32768)
|
75
75
|
changed = true
|
76
|
-
|
76
|
+
watched_file.buffer_extract(data).each do |line|
|
77
77
|
listener.accept(line)
|
78
|
-
@sincedb[
|
78
|
+
@sincedb[watched_file.inode] += (line.bytesize + @delimiter_byte_size)
|
79
79
|
end
|
80
|
+
# watched_file bytes_read tracks the sincedb entry
|
81
|
+
# see TODO in watch.rb
|
82
|
+
watched_file.update_bytes_read(@sincedb[watched_file.inode])
|
80
83
|
rescue EOFError
|
81
84
|
listener.eof
|
82
85
|
break
|
83
86
|
rescue Errno::EWOULDBLOCK, Errno::EINTR
|
84
87
|
listener.error
|
85
88
|
break
|
89
|
+
rescue => e
|
90
|
+
@logger.debug? && @logger.debug("observe_read_file: general error reading #{watched_file.path} - error: #{e.inspect}")
|
91
|
+
listener.error
|
92
|
+
break
|
86
93
|
end
|
87
94
|
end
|
88
95
|
|
data/lib/filewatch/tail_base.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require "filewatch/helper"
|
2
|
-
require "filewatch/buftok"
|
3
2
|
require "filewatch/watch"
|
4
3
|
|
5
4
|
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
@@ -14,14 +13,15 @@ require "JRubyFileExtension.jar" if defined? JRUBY_VERSION
|
|
14
13
|
module FileWatch
|
15
14
|
module TailBase
|
16
15
|
# how often (in seconds) we @logger.warn a failed file open, per path.
|
17
|
-
OPEN_WARN_INTERVAL = ENV
|
18
|
-
ENV["FILEWATCH_OPEN_WARN_INTERVAL"].to_i : 300
|
16
|
+
OPEN_WARN_INTERVAL = ENV.fetch("FILEWATCH_OPEN_WARN_INTERVAL", 300).to_i
|
19
17
|
|
20
18
|
attr_reader :logger
|
21
19
|
|
22
20
|
class NoSinceDBPathGiven < StandardError; end
|
23
21
|
|
24
22
|
public
|
23
|
+
# TODO move sincedb to watch.rb
|
24
|
+
# see TODO there
|
25
25
|
def initialize(opts={})
|
26
26
|
@iswindows = ((RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/) != nil)
|
27
27
|
|
@@ -31,14 +31,12 @@ module FileWatch
|
|
31
31
|
@logger = Logger.new(STDERR)
|
32
32
|
@logger.level = Logger::INFO
|
33
33
|
end
|
34
|
-
@files = {}
|
35
34
|
@lastwarn = Hash.new { |h, k| h[k] = 0 }
|
36
35
|
@buffers = {}
|
37
36
|
@watch = FileWatch::Watch.new
|
38
37
|
@watch.logger = @logger
|
39
|
-
@sincedb = {}
|
40
38
|
@sincedb_last_write = 0
|
41
|
-
@
|
39
|
+
@sincedb = {}
|
42
40
|
@opts = {
|
43
41
|
:sincedb_write_interval => 10,
|
44
42
|
:stat_interval => 1,
|
@@ -57,6 +55,9 @@ module FileWatch
|
|
57
55
|
@watch.exclude(@opts[:exclude])
|
58
56
|
@watch.close_older = @opts[:close_older]
|
59
57
|
@watch.ignore_older = @opts[:ignore_older]
|
58
|
+
@watch.delimiter = @opts[:delimiter]
|
59
|
+
@watch.max_open_files = @opts[:max_open_files]
|
60
|
+
@delimiter_byte_size = @opts[:delimiter].bytesize
|
60
61
|
|
61
62
|
_sincedb_open
|
62
63
|
end # def initialize
|
@@ -74,28 +75,20 @@ module FileWatch
|
|
74
75
|
|
75
76
|
public
|
76
77
|
def sincedb_record_uid(path, stat)
|
77
|
-
|
78
|
-
@
|
79
|
-
return inode
|
78
|
+
# retain this call because its part of the public API
|
79
|
+
@watch.inode(path, stat)
|
80
80
|
end # def sincedb_record_uid
|
81
81
|
|
82
82
|
private
|
83
83
|
|
84
|
-
def
|
85
|
-
|
86
|
-
# (Time.now - stat.mtime) <- in jruby, this does int and float
|
87
|
-
# conversions before the subtraction and returns a float.
|
88
|
-
# so use all ints instead
|
89
|
-
(Time.now.to_i - stat.mtime.to_i) > @opts[:ignore_older]
|
90
|
-
end
|
91
|
-
|
92
|
-
def _open_file(path, event)
|
84
|
+
def _open_file(watched_file, event)
|
85
|
+
path = watched_file.path
|
93
86
|
@logger.debug? && @logger.debug("_open_file: #{path}: opening")
|
94
87
|
begin
|
95
88
|
if @iswindows && defined? JRUBY_VERSION
|
96
|
-
|
89
|
+
watched_file.file_add_opened(Java::RubyFileExt::getRubyFile(path))
|
97
90
|
else
|
98
|
-
|
91
|
+
watched_file.file_add_opened(File.open(path))
|
99
92
|
end
|
100
93
|
rescue
|
101
94
|
# don't emit this message too often. if a file that we can't
|
@@ -106,46 +99,68 @@ module FileWatch
|
|
106
99
|
@logger.warn("failed to open #{path}: #{$!}")
|
107
100
|
@lastwarn[path] = now
|
108
101
|
else
|
109
|
-
@logger.debug? && @logger.debug("(warn supressed) failed to open #{path}: #{
|
102
|
+
@logger.debug? && @logger.debug("(warn supressed) failed to open #{path}: #{$!.inspect}")
|
110
103
|
end
|
111
|
-
|
104
|
+
watched_file.watch # set it back to watch so we can try it again
|
112
105
|
return false
|
113
106
|
end
|
107
|
+
_add_to_sincedb(watched_file, event)
|
108
|
+
true
|
109
|
+
end # def _open_file
|
114
110
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
111
|
+
def _add_to_sincedb(watched_file, event)
|
112
|
+
# called when newly discovered files are opened
|
113
|
+
stat = watched_file.filestat
|
114
|
+
sincedb_key = watched_file.inode
|
115
|
+
path = watched_file.path
|
116
|
+
|
117
|
+
if @sincedb.member?(sincedb_key)
|
118
|
+
# we have seen this inode before
|
119
|
+
# but this is a new watched_file
|
120
|
+
# and we can't tell if its contents are the same
|
121
|
+
# as another file we have watched before.
|
122
|
+
last_read_size = @sincedb[sincedb_key]
|
123
|
+
@logger.debug? && @logger.debug("#{path}: sincedb last value #{@sincedb[sincedb_key]}, cur size #{stat.size}")
|
124
|
+
if stat.size > last_read_size
|
125
|
+
# 1) it could really be a new file with lots of new content
|
126
|
+
# 2) it could have old content that was read plus new that is not
|
127
|
+
@logger.debug? && @logger.debug("#{path}: sincedb: seeking to #{last_read_size}")
|
128
|
+
watched_file.file_seek(last_read_size) # going with 2
|
129
|
+
watched_file.update_bytes_read(last_read_size)
|
130
|
+
elsif stat.size == last_read_size
|
131
|
+
# 1) it could have old content that was read
|
132
|
+
# 2) it could have new content that happens to be the same size
|
133
|
+
@logger.debug? && @logger.debug("#{path}: sincedb: seeking to #{last_read_size}")
|
134
|
+
watched_file.file_seek(last_read_size) # going with 1.
|
135
|
+
watched_file.update_bytes_read(last_read_size)
|
126
136
|
else
|
137
|
+
# it seems to be a new file with less content
|
127
138
|
@logger.debug? && @logger.debug("#{path}: last value size is greater than current value, starting over")
|
128
|
-
@sincedb[
|
139
|
+
@sincedb[sincedb_key] = 0
|
140
|
+
watched_file.update_bytes_read(0) if watched_file.bytes_read != 0
|
129
141
|
end
|
130
|
-
elsif event == :create_initial
|
142
|
+
elsif event == :create_initial
|
131
143
|
if @opts[:start_new_files_at] == :beginning
|
132
144
|
@logger.debug? && @logger.debug("#{path}: initial create, no sincedb, seeking to beginning of file")
|
133
|
-
|
134
|
-
@sincedb[
|
145
|
+
watched_file.file_seek(0)
|
146
|
+
@sincedb[sincedb_key] = 0
|
135
147
|
else
|
136
148
|
# seek to end
|
137
149
|
@logger.debug? && @logger.debug("#{path}: initial create, no sincedb, seeking to end #{stat.size}")
|
138
|
-
|
139
|
-
@sincedb[
|
150
|
+
watched_file.file_seek(stat.size)
|
151
|
+
@sincedb[sincedb_key] = stat.size
|
140
152
|
end
|
141
|
-
elsif event == :create
|
142
|
-
@sincedb[
|
153
|
+
elsif event == :create
|
154
|
+
@sincedb[sincedb_key] = 0
|
155
|
+
elsif event == :modify && @sincedb[sincedb_key].nil?
|
156
|
+
@sincedb[sincedb_key] = 0
|
157
|
+
elsif event == :unignore
|
158
|
+
@sincedb[sincedb_key] = watched_file.bytes_read
|
143
159
|
else
|
144
160
|
@logger.debug? && @logger.debug("#{path}: staying at position 0, no sincedb")
|
145
161
|
end
|
146
|
-
|
147
162
|
return true
|
148
|
-
end # def
|
163
|
+
end # def _add_to_sincedb
|
149
164
|
|
150
165
|
public
|
151
166
|
def sincedb_write(reason=nil)
|
@@ -157,30 +172,34 @@ module FileWatch
|
|
157
172
|
def _sincedb_open
|
158
173
|
path = @opts[:sincedb_path]
|
159
174
|
begin
|
160
|
-
|
175
|
+
File.open(path) do |db|
|
176
|
+
@logger.debug? && @logger.debug("_sincedb_open: reading from #{path}")
|
177
|
+
db.each do |line|
|
178
|
+
ino, dev_major, dev_minor, pos = line.split(" ", 4)
|
179
|
+
sincedb_key = [ino, dev_major.to_i, dev_minor.to_i]
|
180
|
+
@logger.debug? && @logger.debug("_sincedb_open: setting #{sincedb_key.inspect} to #{pos.to_i}")
|
181
|
+
@sincedb[sincedb_key] = pos.to_i
|
182
|
+
end
|
183
|
+
end
|
161
184
|
rescue
|
162
185
|
#No existing sincedb to load
|
163
|
-
@logger.debug? && @logger.debug("_sincedb_open: #{path}: #{$!}")
|
164
|
-
return
|
165
|
-
end
|
166
|
-
|
167
|
-
@logger.debug? && @logger.debug("_sincedb_open: reading from #{path}")
|
168
|
-
db.each do |line|
|
169
|
-
ino, dev_major, dev_minor, pos = line.split(" ", 4)
|
170
|
-
sincedb_record_uid = [ino, dev_major.to_i, dev_minor.to_i]
|
171
|
-
@logger.debug? && @logger.debug("_sincedb_open: setting #{sincedb_record_uid.inspect} to #{pos.to_i}")
|
172
|
-
@sincedb[sincedb_record_uid] = pos.to_i
|
186
|
+
@logger.debug? && @logger.debug("_sincedb_open: error: #{path}: #{$!}")
|
173
187
|
end
|
174
|
-
db.close
|
175
188
|
end # def _sincedb_open
|
176
189
|
|
177
190
|
private
|
178
191
|
def _sincedb_write
|
179
192
|
path = @opts[:sincedb_path]
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
193
|
+
begin
|
194
|
+
if @iswindows || File.device?(path)
|
195
|
+
IO.write(path, serialize_sincedb, 0)
|
196
|
+
else
|
197
|
+
File.atomic_write(path) {|file| file.write(serialize_sincedb) }
|
198
|
+
end
|
199
|
+
rescue Errno::EACCES
|
200
|
+
# probably no file handles free
|
201
|
+
# maybe it will work next time
|
202
|
+
@logger.debug? && @logger.debug("_sincedb_write: error: #{path}: #{$!}")
|
184
203
|
end
|
185
204
|
end # def _sincedb_write
|
186
205
|
|
@@ -189,13 +208,13 @@ module FileWatch
|
|
189
208
|
# it should be called for clean up
|
190
209
|
# before the instance is disposed of.
|
191
210
|
def quit
|
211
|
+
@watch.quit # <-- should close all the files
|
212
|
+
# and that should allow the sincedb_write to succeed if it could not before
|
192
213
|
_sincedb_write
|
193
|
-
@watch.quit
|
194
|
-
@files.each {|path, file| file.close }
|
195
|
-
@files.clear
|
196
214
|
end # def quit
|
197
215
|
|
198
216
|
public
|
217
|
+
|
199
218
|
# close_file(path) is to be used by external code
|
200
219
|
# when it knows that it is completely done with a file.
|
201
220
|
# Other files or folders may still be being watched.
|
@@ -204,10 +223,7 @@ module FileWatch
|
|
204
223
|
# The sysadmin should rename, move or delete the file.
|
205
224
|
def close_file(path)
|
206
225
|
@watch.unwatch(path)
|
207
|
-
file = @files.delete(path)
|
208
|
-
return if file.nil?
|
209
226
|
_sincedb_write
|
210
|
-
file.close
|
211
227
|
end
|
212
228
|
|
213
229
|
private
|
data/lib/filewatch/watch.rb
CHANGED
@@ -1,59 +1,42 @@
|
|
1
1
|
require "logger"
|
2
|
+
require_relative 'watched_file'
|
3
|
+
|
2
4
|
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
3
5
|
require "filewatch/winhelper"
|
6
|
+
FILEWATCH_INODE_METHOD = :win_inode
|
7
|
+
else
|
8
|
+
FILEWATCH_INODE_METHOD = :nix_inode
|
4
9
|
end
|
5
10
|
|
6
11
|
module FileWatch
|
12
|
+
# TODO make a WatchedFilesDb class that holds the watched_files instead of a hash
|
13
|
+
# it should support an 'identity' of path + inode
|
14
|
+
# it should be serializable instead of the sincedb
|
15
|
+
# it should be deserializable to recreate the exact state all files were in as last seen
|
16
|
+
# some parts of the each method should be handled by it, e.g.
|
17
|
+
# wfs_db.<state>_iterator{|wf| }, trapping the Errno::ENOENT, auto_delete and yield wtached_file
|
7
18
|
class Watch
|
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 clear_timeout
|
34
|
-
@timeout_sent = false
|
35
|
-
end
|
36
19
|
|
37
|
-
|
38
|
-
@create_sent
|
39
|
-
end
|
20
|
+
MAX_FILES_WARN_INTERVAL = ENV.fetch("FILEWATCH_MAX_FILES_WARN_INTERVAL", 20).to_i
|
40
21
|
|
41
|
-
|
42
|
-
|
43
|
-
|
22
|
+
def self.win_inode(path, stat)
|
23
|
+
fileId = Winhelper.GetWindowsUniqueFileIdentifier(path)
|
24
|
+
[fileId, 0, 0] # dev_* doesn't make sense on Windows
|
25
|
+
end
|
44
26
|
|
45
|
-
|
46
|
-
|
47
|
-
|
27
|
+
def self.nix_inode(path, stat)
|
28
|
+
[stat.ino.to_s, stat.dev_major, stat.dev_minor]
|
29
|
+
end
|
48
30
|
|
49
|
-
|
31
|
+
def self.inode(path, stat)
|
32
|
+
send(FILEWATCH_INODE_METHOD, path, stat)
|
50
33
|
end
|
51
34
|
|
52
|
-
attr_accessor :logger
|
35
|
+
attr_accessor :logger
|
36
|
+
attr_accessor :delimiter
|
37
|
+
attr_reader :max_active
|
53
38
|
|
54
|
-
public
|
55
39
|
def initialize(opts={})
|
56
|
-
@iswindows = ((RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/) != nil)
|
57
40
|
if opts[:logger]
|
58
41
|
@logger = opts[:logger]
|
59
42
|
else
|
@@ -62,62 +45,65 @@ module FileWatch
|
|
62
45
|
end
|
63
46
|
@watching = []
|
64
47
|
@exclude = []
|
65
|
-
@files = Hash.new
|
66
|
-
@unwatched = Hash.new
|
48
|
+
@files = Hash.new
|
67
49
|
# we need to be threadsafe about the mutation
|
68
50
|
# of the above 2 ivars because the public
|
69
|
-
# methods each, discover
|
51
|
+
# methods each, discover and watch
|
70
52
|
# can be called from different threads.
|
71
53
|
@lock = Mutex.new
|
72
54
|
# we need to be threadsafe about the quit mutation
|
73
55
|
@quit = false
|
74
56
|
@quit_lock = Mutex.new
|
57
|
+
self.max_open_files = ENV["FILEWATCH_MAX_OPEN_FILES"].to_i
|
58
|
+
@lastwarn_max_files = 0
|
75
59
|
end # def initialize
|
76
60
|
|
77
61
|
public
|
62
|
+
|
63
|
+
def max_open_files=(value)
|
64
|
+
val = value.to_i
|
65
|
+
val = 4095 if value.nil? || val <= 0
|
66
|
+
@max_warn_msg = "Reached open files limit: #{val}, set by the 'max_open_files' option or default"
|
67
|
+
@max_active = val
|
68
|
+
end
|
69
|
+
|
70
|
+
def ignore_older=(value)
|
71
|
+
#nil is allowed but 0 and negatives are made nil
|
72
|
+
if !value.nil?
|
73
|
+
val = value.to_f
|
74
|
+
val = val <= 0 ? nil : val
|
75
|
+
end
|
76
|
+
@ignore_older = val
|
77
|
+
end
|
78
|
+
|
79
|
+
def close_older=(value)
|
80
|
+
if !value.nil?
|
81
|
+
val = value.to_f
|
82
|
+
val = val <= 0 ? nil : val
|
83
|
+
end
|
84
|
+
@close_older = val
|
85
|
+
end
|
86
|
+
|
78
87
|
def exclude(path)
|
79
88
|
path.to_a.each { |p| @exclude << p }
|
80
89
|
end
|
81
90
|
|
82
|
-
public
|
83
91
|
def watch(path)
|
84
92
|
synchronized do
|
85
93
|
if !@watching.member?(path)
|
86
94
|
@watching << path
|
87
95
|
_discover_file(path) do |filepath, stat|
|
88
|
-
WatchedFile.new_initial(
|
96
|
+
WatchedFile.new_initial(
|
97
|
+
filepath, inode(filepath, stat), stat
|
98
|
+
).init_vars(@delimiter, @ignore_older, @close_older)
|
89
99
|
end
|
90
100
|
end
|
91
101
|
end
|
92
102
|
return true
|
93
103
|
end # def watch
|
94
104
|
|
95
|
-
def
|
96
|
-
|
97
|
-
result = false
|
98
|
-
if @watching.delete(path)
|
99
|
-
_globbed_files(path).each do |file|
|
100
|
-
deleted = @files.delete(file)
|
101
|
-
@unwatched[file] = deleted if deleted
|
102
|
-
end
|
103
|
-
result = true
|
104
|
-
else
|
105
|
-
result = @files.delete(path)
|
106
|
-
@unwatched[path] = result if result
|
107
|
-
end
|
108
|
-
return !!result
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
public
|
113
|
-
def inode(path,stat)
|
114
|
-
if @iswindows
|
115
|
-
fileId = Winhelper.GetWindowsUniqueFileIdentifier(path)
|
116
|
-
inode = [fileId, 0, 0] # dev_* doesn't make sense on Windows
|
117
|
-
else
|
118
|
-
inode = [stat.ino.to_s, stat.dev_major, stat.dev_minor]
|
119
|
-
end
|
120
|
-
return inode
|
105
|
+
def inode(path, stat)
|
106
|
+
self.class.inode(path, stat)
|
121
107
|
end
|
122
108
|
|
123
109
|
# Calls &block with params [event_type, path]
|
@@ -126,77 +112,147 @@ module FileWatch
|
|
126
112
|
# :create - file is created (new file after initial globs, start at 0)
|
127
113
|
# :modify - file is modified (size increases)
|
128
114
|
# :delete - file is deleted
|
129
|
-
|
115
|
+
# :timeout - file is closable
|
116
|
+
# :unignore - file was ignored, but since then it received new content
|
130
117
|
def each(&block)
|
131
118
|
synchronized do
|
119
|
+
return if @files.empty?
|
120
|
+
|
121
|
+
file_deleteable = []
|
122
|
+
# creates this array just once
|
123
|
+
watched_files = @files.values
|
124
|
+
|
125
|
+
# look at the closed to see if its changed
|
126
|
+
watched_files.select {|wf| wf.closed? }.each do |watched_file|
|
127
|
+
path = watched_file.path
|
128
|
+
begin
|
129
|
+
stat = watched_file.restat
|
130
|
+
if watched_file.size_changed? || watched_file.inode_changed?(inode(path,stat))
|
131
|
+
# if the closed file changed, move it to the watched state
|
132
|
+
# not to active state because we want to use MAX_OPEN_FILES throttling.
|
133
|
+
watched_file.watch
|
134
|
+
end
|
135
|
+
rescue Errno::ENOENT
|
136
|
+
# file has gone away or we can't read it anymore.
|
137
|
+
file_deleteable << path
|
138
|
+
@logger.debug? && @logger.debug("each: closed: stat failed: #{path}: (#{$!}), deleting from @files")
|
139
|
+
rescue => e
|
140
|
+
@logger.debug? && @logger.debug("each: closed: stat failed: #{path}: (#{e.inspect})")
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# look at the ignored to see if its changed
|
145
|
+
watched_files.select {|wf| wf.ignored? }.each do |watched_file|
|
146
|
+
path = watched_file.path
|
147
|
+
begin
|
148
|
+
stat = watched_file.restat
|
149
|
+
if watched_file.size_changed? || watched_file.inode_changed?(inode(path,stat))
|
150
|
+
# if the ignored file changed, move it to the watched state
|
151
|
+
# not to active state because we want to use MAX_OPEN_FILES throttling.
|
152
|
+
# this file has not been yielded to the block yet
|
153
|
+
# but we must have the tail to start from the end, so when the file
|
154
|
+
# was first ignored we updated the bytes_read to the stat.size at that time.
|
155
|
+
# by adding this to the sincedb so that the subsequent modify
|
156
|
+
# event can detect the change
|
157
|
+
watched_file.watch
|
158
|
+
yield(:unignore, watched_file)
|
159
|
+
end
|
160
|
+
rescue Errno::ENOENT
|
161
|
+
# file has gone away or we can't read it anymore.
|
162
|
+
file_deleteable << path
|
163
|
+
@logger.debug? && @logger.debug("each: ignored: stat failed: #{path}: (#{$!}), deleting from @files")
|
164
|
+
rescue => e
|
165
|
+
@logger.debug? && @logger.debug("each: ignored: stat failed: #{path}: (#{e.inspect})")
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
132
169
|
# Send any creates.
|
133
|
-
@
|
134
|
-
|
170
|
+
if (to_take = @max_active - watched_files.count{|wf| wf.active?}) > 0
|
171
|
+
watched_files.select {|wf| wf.watched? }.take(to_take).each do |watched_file|
|
172
|
+
watched_file.activate
|
173
|
+
# don't do create again
|
174
|
+
next if watched_file.state_history_any?(:closed, :ignored)
|
175
|
+
# if the file can't be opened during the yield
|
176
|
+
# its state is set back to watched
|
135
177
|
if watched_file.initial?
|
136
|
-
yield(:create_initial,
|
178
|
+
yield(:create_initial, watched_file)
|
179
|
+
else
|
180
|
+
yield(:create, watched_file)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
else
|
184
|
+
now = Time.now.to_i
|
185
|
+
if (now - @lastwarn_max_files) > MAX_FILES_WARN_INTERVAL
|
186
|
+
waiting = @files.size - @max_active
|
187
|
+
specific = if @close_older.nil?
|
188
|
+
", try setting close_older. There are #{waiting} unopened files"
|
137
189
|
else
|
138
|
-
|
190
|
+
", files yet to open: #{waiting}"
|
139
191
|
end
|
140
|
-
|
192
|
+
@logger.warn(@max_warn_msg + specific)
|
193
|
+
@lastwarn_max_files = now
|
141
194
|
end
|
142
195
|
end
|
143
196
|
|
144
|
-
|
197
|
+
# wf.active means the actual files were opened
|
198
|
+
# and have been read once - unless they were empty at the time
|
199
|
+
watched_files.select {|wf| wf.active? }.each do |watched_file|
|
200
|
+
path = watched_file.path
|
145
201
|
begin
|
146
|
-
stat =
|
202
|
+
stat = watched_file.restat
|
147
203
|
rescue Errno::ENOENT
|
148
204
|
# file has gone away or we can't read it anymore.
|
149
|
-
|
150
|
-
@logger.debug? && @logger.debug("#{path}:
|
151
|
-
|
205
|
+
file_deleteable << path
|
206
|
+
@logger.debug? && @logger.debug("each: active: stat failed: #{path}: (#{$!}), deleting from @files")
|
207
|
+
watched_file.unwatch
|
208
|
+
yield(:delete, watched_file)
|
209
|
+
next
|
210
|
+
rescue => e
|
211
|
+
@logger.debug? && @logger.debug("each: active: stat failed: #{path}: (#{e.inspect})")
|
152
212
|
next
|
153
213
|
end
|
154
214
|
|
155
|
-
if file_closable?
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
watched_file.timeout_sent = true
|
160
|
-
end
|
215
|
+
if watched_file.file_closable?
|
216
|
+
@logger.debug? && @logger.debug("each: active: file expired: #{path}")
|
217
|
+
yield(:timeout, watched_file)
|
218
|
+
watched_file.close
|
161
219
|
next
|
162
220
|
end
|
163
221
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
if
|
168
|
-
@logger.debug? && @logger.debug("#{path}: old inode was #{watched_file.inode.inspect}, new is #{
|
169
|
-
|
170
|
-
yield(:
|
171
|
-
|
172
|
-
elsif stat.size <
|
173
|
-
@logger.debug? && @logger.debug("#{path}:
|
174
|
-
yield(:delete,
|
175
|
-
yield(:create,
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
yield(:modify, path)
|
180
|
-
# if there is a material change to the file, re-enable timeout
|
181
|
-
watched_file.clear_timeout
|
182
|
-
watched_file.update(stat, inode)
|
222
|
+
_inode = inode(path,stat)
|
223
|
+
read_thus_far = watched_file.bytes_read
|
224
|
+
# we don't update the size here, its updated when we actually read
|
225
|
+
if watched_file.inode_changed?(_inode)
|
226
|
+
@logger.debug? && @logger.debug("each: new inode: #{path}: old inode was #{watched_file.inode.inspect}, new is #{_inode.inspect}")
|
227
|
+
watched_file.update_inode(_inode)
|
228
|
+
yield(:delete, watched_file)
|
229
|
+
yield(:create, watched_file)
|
230
|
+
elsif stat.size < read_thus_far
|
231
|
+
@logger.debug? && @logger.debug("each: file rolled: #{path}: new size is #{stat.size}, old size #{read_thus_far}")
|
232
|
+
yield(:delete, watched_file)
|
233
|
+
yield(:create, watched_file)
|
234
|
+
elsif stat.size > read_thus_far
|
235
|
+
@logger.debug? && @logger.debug("each: file grew: #{path}: old size #{read_thus_far}, new size #{stat.size}")
|
236
|
+
yield(:modify, watched_file)
|
183
237
|
end
|
184
238
|
end
|
239
|
+
|
240
|
+
file_deleteable.each {|f| @files.delete(f)}
|
185
241
|
end
|
186
242
|
end # def each
|
187
243
|
|
188
|
-
public
|
189
244
|
def discover
|
190
245
|
synchronized do
|
191
246
|
@watching.each do |path|
|
192
247
|
_discover_file(path) do |filepath, stat|
|
193
|
-
WatchedFile.new_ongoing(
|
248
|
+
WatchedFile.new_ongoing(
|
249
|
+
filepath, inode(filepath, stat), stat
|
250
|
+
).init_vars(@delimiter, @ignore_older, @close_older)
|
194
251
|
end
|
195
252
|
end
|
196
253
|
end
|
197
254
|
end
|
198
255
|
|
199
|
-
public
|
200
256
|
def subscribe(stat_interval = 1, discover_interval = 5, &block)
|
201
257
|
glob = 0
|
202
258
|
reset_quit
|
@@ -208,80 +264,62 @@ module FileWatch
|
|
208
264
|
discover
|
209
265
|
glob = 0
|
210
266
|
end
|
211
|
-
|
267
|
+
break if quit?
|
212
268
|
sleep(stat_interval)
|
213
269
|
end
|
270
|
+
@files.values.each(&:file_close)
|
214
271
|
end # def subscribe
|
215
272
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
end
|
220
|
-
|
221
|
-
def file_ignorable?(stat)
|
222
|
-
return false unless expiry_ignore_enabled?
|
223
|
-
# (Time.now - stat.mtime) <- in jruby, this does int and float
|
224
|
-
# conversions before the subtraction and returns a float.
|
225
|
-
# so use all ints instead
|
226
|
-
(Time.now.to_i - stat.mtime.to_i) > @ignore_older
|
227
|
-
end
|
273
|
+
def quit
|
274
|
+
@quit_lock.synchronize { @quit = true }
|
275
|
+
end # def quit
|
228
276
|
|
229
|
-
def
|
230
|
-
|
231
|
-
# (Time.now - stat.mtime) <- in jruby, this does int and float
|
232
|
-
# conversions before the subtraction and returns a float.
|
233
|
-
# so use all ints instead
|
234
|
-
(Time.now.to_i - stat.mtime.to_i) > @close_older
|
277
|
+
def quit?
|
278
|
+
@quit_lock.synchronize { @quit }
|
235
279
|
end
|
236
280
|
|
237
281
|
private
|
282
|
+
|
238
283
|
def _discover_file(path)
|
239
284
|
_globbed_files(path).each do |file|
|
240
|
-
next if @files.member?(file)
|
241
|
-
next if @unwatched.member?(file)
|
242
285
|
next unless File.file?(file)
|
243
|
-
|
244
|
-
@
|
286
|
+
new_discovery = false
|
287
|
+
if @files.member?(file)
|
288
|
+
watched_file = @files[file]
|
289
|
+
else
|
290
|
+
@logger.debug? && @logger.debug("_discover_file: #{path}: new: #{file} (exclude is #{@exclude.inspect})")
|
291
|
+
# let the caller build the object in its context
|
292
|
+
new_discovery = true
|
293
|
+
watched_file = yield(file, File::Stat.new(file))
|
294
|
+
end
|
245
295
|
|
246
296
|
skip = false
|
247
297
|
@exclude.each do |pattern|
|
248
298
|
if File.fnmatch?(pattern, File.basename(file))
|
249
299
|
@logger.debug? && @logger.debug("_discover_file: #{file}: skipping because it " +
|
250
|
-
"matches exclude #{pattern}")
|
300
|
+
"matches exclude #{pattern}") if new_discovery
|
251
301
|
skip = true
|
302
|
+
watched_file.unwatch
|
252
303
|
break
|
253
304
|
end
|
254
305
|
end
|
255
306
|
next if skip
|
256
307
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
watched_file.update(stat)
|
308
|
+
if new_discovery
|
309
|
+
if watched_file.file_ignorable?
|
310
|
+
@logger.debug? && @logger.debug("_discover_file: #{file}: skipping because it was last modified more than #{@ignore_older} seconds ago")
|
311
|
+
# on discovery we put watched_file into the ignored state and that
|
312
|
+
# updates the size from the internal stat
|
313
|
+
# so the existing contents are not read.
|
314
|
+
# because, normally, a newly discovered file will
|
315
|
+
# have a watched_file size of zero
|
316
|
+
watched_file.ignore
|
317
|
+
end
|
318
|
+
@files[file] = watched_file
|
269
319
|
end
|
270
|
-
|
271
|
-
@files[file] = watched_file
|
272
320
|
end
|
273
321
|
end # def _discover_file
|
274
322
|
|
275
|
-
private
|
276
|
-
def expiry_close_enabled?
|
277
|
-
!@close_older.nil?
|
278
|
-
end
|
279
|
-
|
280
|
-
private
|
281
|
-
def expiry_ignore_enabled?
|
282
|
-
!@ignore_older.nil?
|
283
|
-
end
|
284
|
-
|
285
323
|
private
|
286
324
|
def _globbed_files(path)
|
287
325
|
globbed_dirs = Dir.glob(path)
|
@@ -299,19 +337,11 @@ module FileWatch
|
|
299
337
|
@lock.synchronize { block.call }
|
300
338
|
end
|
301
339
|
|
302
|
-
|
303
|
-
def quit?
|
304
|
-
@quit_lock.synchronize { @quit }
|
305
|
-
end
|
340
|
+
public
|
306
341
|
|
307
342
|
private
|
308
343
|
def reset_quit
|
309
344
|
@quit_lock.synchronize { @quit = false }
|
310
345
|
end
|
311
|
-
|
312
|
-
public
|
313
|
-
def quit
|
314
|
-
@quit_lock.synchronize { @quit = true }
|
315
|
-
end # def quit
|
316
346
|
end # class Watch
|
317
347
|
end # module FileWatch
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require "filewatch/buftok"
|
2
|
+
|
3
|
+
module FileWatch
|
4
|
+
class WatchedFile
|
5
|
+
def self.new_initial(path, inode, stat)
|
6
|
+
new(path, inode, stat, true)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.new_ongoing(path, inode, stat)
|
10
|
+
new(path, inode, stat, false)
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :bytes_read, :inode, :state, :file, :buffer, :state_history
|
14
|
+
attr_reader :path, :filestat, :accessed_at
|
15
|
+
attr_accessor :close_older, :ignore_older, :delimiter
|
16
|
+
|
17
|
+
def delimiter
|
18
|
+
@delimiter
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(path, inode, stat, initial)
|
22
|
+
@path = path
|
23
|
+
@bytes_read = 0
|
24
|
+
@inode = inode
|
25
|
+
@initial = initial
|
26
|
+
@state_history = []
|
27
|
+
@state = :watched
|
28
|
+
@filestat = stat
|
29
|
+
set_accessed_at
|
30
|
+
end
|
31
|
+
|
32
|
+
def init_vars(delim, ignore_o, close_o)
|
33
|
+
@delimiter = delim
|
34
|
+
@ignore_older = ignore_o
|
35
|
+
@close_older = close_o
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def set_accessed_at
|
40
|
+
@accessed_at = Time.now.to_f
|
41
|
+
end
|
42
|
+
|
43
|
+
def initial?
|
44
|
+
@initial
|
45
|
+
end
|
46
|
+
|
47
|
+
def size_changed?
|
48
|
+
filestat.size != bytes_read
|
49
|
+
end
|
50
|
+
|
51
|
+
def inode_changed?(value)
|
52
|
+
self.inode != value
|
53
|
+
end
|
54
|
+
|
55
|
+
def file_add_opened(rubyfile)
|
56
|
+
@file = rubyfile
|
57
|
+
@buffer = FileWatch::BufferedTokenizer.new(delimiter || "\n")
|
58
|
+
end
|
59
|
+
|
60
|
+
def file_close
|
61
|
+
return if @file.nil? || @file.closed?
|
62
|
+
@file.close
|
63
|
+
@file = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def file_seek(amount, whence = IO::SEEK_SET)
|
67
|
+
@file.sysseek(amount, whence)
|
68
|
+
end
|
69
|
+
|
70
|
+
def file_read(amount)
|
71
|
+
set_accessed_at
|
72
|
+
@file.sysread(amount)
|
73
|
+
end
|
74
|
+
|
75
|
+
def file_open?
|
76
|
+
!@file.nil? && !@file.closed?
|
77
|
+
end
|
78
|
+
|
79
|
+
def update_bytes_read(total_bytes_read)
|
80
|
+
return if total_bytes_read.nil?
|
81
|
+
@bytes_read = total_bytes_read
|
82
|
+
end
|
83
|
+
|
84
|
+
def buffer_extract(data)
|
85
|
+
@buffer.extract(data)
|
86
|
+
end
|
87
|
+
|
88
|
+
def update_inode(_inode)
|
89
|
+
@inode = _inode
|
90
|
+
end
|
91
|
+
|
92
|
+
def activate
|
93
|
+
set_state :active
|
94
|
+
end
|
95
|
+
|
96
|
+
def ignore
|
97
|
+
set_state :ignored
|
98
|
+
@bytes_read = @filestat.size
|
99
|
+
end
|
100
|
+
|
101
|
+
def close
|
102
|
+
set_state :closed
|
103
|
+
end
|
104
|
+
|
105
|
+
def watch
|
106
|
+
set_state :watched
|
107
|
+
end
|
108
|
+
|
109
|
+
def unwatch
|
110
|
+
set_state :unwatched
|
111
|
+
end
|
112
|
+
|
113
|
+
def active?
|
114
|
+
@state == :active
|
115
|
+
end
|
116
|
+
|
117
|
+
def ignored?
|
118
|
+
@state == :ignored
|
119
|
+
end
|
120
|
+
|
121
|
+
def closed?
|
122
|
+
@state == :closed
|
123
|
+
end
|
124
|
+
|
125
|
+
def watched?
|
126
|
+
@state == :watched
|
127
|
+
end
|
128
|
+
|
129
|
+
def unwatched?
|
130
|
+
@state == :unwatched
|
131
|
+
end
|
132
|
+
|
133
|
+
def expiry_close_enabled?
|
134
|
+
!@close_older.nil?
|
135
|
+
end
|
136
|
+
|
137
|
+
def expiry_ignore_enabled?
|
138
|
+
!@ignore_older.nil?
|
139
|
+
end
|
140
|
+
|
141
|
+
def restat
|
142
|
+
@filestat = File::Stat.new(path)
|
143
|
+
end
|
144
|
+
|
145
|
+
def set_state(value)
|
146
|
+
@state_history << @state
|
147
|
+
@state = value
|
148
|
+
end
|
149
|
+
|
150
|
+
def state_history_any?(*previous)
|
151
|
+
(@state_history & previous).any?
|
152
|
+
end
|
153
|
+
|
154
|
+
def file_closable?
|
155
|
+
file_can_close? && !size_changed?
|
156
|
+
end
|
157
|
+
|
158
|
+
def file_ignorable?
|
159
|
+
return false unless expiry_ignore_enabled?
|
160
|
+
# (Time.now - stat.mtime) <- in jruby, this does int and float
|
161
|
+
# conversions before the subtraction and returns a float.
|
162
|
+
# so use all floats upfront
|
163
|
+
(Time.now.to_f - filestat.mtime.to_f) > ignore_older
|
164
|
+
end
|
165
|
+
|
166
|
+
def file_can_close?
|
167
|
+
return false unless expiry_close_enabled?
|
168
|
+
(Time.now.to_f - @accessed_at) > close_older
|
169
|
+
end
|
170
|
+
|
171
|
+
def to_s() inspect; end
|
172
|
+
end
|
173
|
+
end
|
@@ -8,39 +8,41 @@ module FileWatch
|
|
8
8
|
def subscribe(&block)
|
9
9
|
# subscribe(stat_interval = 1, discover_interval = 5, &block)
|
10
10
|
@watch.subscribe(@opts[:stat_interval],
|
11
|
-
@opts[:discover_interval]) do |event,
|
11
|
+
@opts[:discover_interval]) do |event, watched_file|
|
12
|
+
path = watched_file.path
|
13
|
+
file_is_open = watched_file.file_open?
|
14
|
+
|
12
15
|
case event
|
16
|
+
when :unignore
|
17
|
+
_add_to_sincedb(watched_file, event)
|
13
18
|
when :create, :create_initial
|
14
|
-
if
|
15
|
-
@logger.debug? && @logger.debug("#{event} for #{path}: already
|
19
|
+
if file_is_open
|
20
|
+
@logger.debug? && @logger.debug("#{event} for #{path}: file already open")
|
16
21
|
next
|
17
22
|
end
|
18
|
-
if _open_file(
|
19
|
-
yield_read_file(
|
23
|
+
if _open_file(watched_file, event)
|
24
|
+
yield_read_file(watched_file, &block)
|
20
25
|
end
|
21
26
|
when :modify
|
22
|
-
if
|
23
|
-
@logger.debug? && @logger.debug(":modify for #{path},
|
24
|
-
if _open_file(
|
25
|
-
yield_read_file(
|
27
|
+
if !file_is_open
|
28
|
+
@logger.debug? && @logger.debug(":modify for #{path}, file is not open, opening now")
|
29
|
+
if _open_file(watched_file, event)
|
30
|
+
yield_read_file(watched_file, &block)
|
26
31
|
end
|
27
32
|
else
|
28
|
-
yield_read_file(
|
33
|
+
yield_read_file(watched_file, &block)
|
29
34
|
end
|
30
35
|
when :delete
|
31
|
-
|
32
|
-
|
33
|
-
yield_read_file(
|
34
|
-
|
36
|
+
if file_is_open
|
37
|
+
@logger.debug? && @logger.debug(":delete for #{path}, closing file")
|
38
|
+
yield_read_file(watched_file, &block)
|
39
|
+
watched_file.file_close
|
40
|
+
else
|
41
|
+
@logger.debug? && @logger.debug(":delete for #{path}, file already closed")
|
35
42
|
end
|
36
|
-
@files.delete(path)
|
37
|
-
@statcache.delete(path)
|
38
43
|
when :timeout
|
39
|
-
@logger.debug? && @logger.debug(":timeout for
|
40
|
-
|
41
|
-
deleted.close
|
42
|
-
end
|
43
|
-
@statcache.delete(path)
|
44
|
+
@logger.debug? && @logger.debug(":timeout for #{path}, closing file")
|
45
|
+
watched_file.file_close
|
44
46
|
else
|
45
47
|
@logger.warn("unknown event type #{event} for #{path}")
|
46
48
|
end
|
@@ -48,18 +50,19 @@ module FileWatch
|
|
48
50
|
end # def subscribe
|
49
51
|
|
50
52
|
private
|
51
|
-
def yield_read_file(
|
52
|
-
@buffers[path] ||= FileWatch::BufferedTokenizer.new(@opts[:delimiter])
|
53
|
-
delimiter_byte_size = @opts[:delimiter].bytesize
|
53
|
+
def yield_read_file(watched_file, &block)
|
54
54
|
changed = false
|
55
55
|
loop do
|
56
56
|
begin
|
57
|
-
data =
|
57
|
+
data = watched_file.file_read(32768)
|
58
58
|
changed = true
|
59
|
-
|
60
|
-
yield(path, line)
|
61
|
-
@sincedb[
|
59
|
+
watched_file.buffer_extract(data).each do |line|
|
60
|
+
yield(watched_file.path, line)
|
61
|
+
@sincedb[watched_file.inode] += (line.bytesize + @delimiter_byte_size)
|
62
62
|
end
|
63
|
+
# watched_file bytes_read tracks the sincedb entry
|
64
|
+
# see TODO in watch.rb
|
65
|
+
watched_file.update_bytes_read(@sincedb[watched_file.inode])
|
63
66
|
rescue Errno::EWOULDBLOCK, Errno::EINTR, EOFError
|
64
67
|
break
|
65
68
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: filewatch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jordan Sissel
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-01-
|
12
|
+
date: 2016-01-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -42,6 +42,7 @@ files:
|
|
42
42
|
- lib/filewatch/tail.rb
|
43
43
|
- lib/filewatch/tail_base.rb
|
44
44
|
- lib/filewatch/watch.rb
|
45
|
+
- lib/filewatch/watched_file.rb
|
45
46
|
- lib/filewatch/winhelper.rb
|
46
47
|
- lib/filewatch/yielding_tail.rb
|
47
48
|
- test/filewatch/tail.rb
|