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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d9bf179bb78fa086471f144cc90404a616076d09
|
4
|
+
data.tar.gz: e0579d790be4bc6a400f4db10097cf610fbc219a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 49599f9831b7f87285e819f926f5748f318dcd636313968f00013dc62bf969f43fa74e119374de73bc36a9ef7f39f5ec198f15397892a48e605287fdd216b828
|
7
|
+
data.tar.gz: 80afb0abad3f869aeb7461f7de3d9e9e49b9bbaf010d9c37f4a9803f5cd3d006224aa65d56ac789869a92f0efe974558f16d6f5185566bc686447e09264b8e96
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'filewatch/tail_base'
|
2
|
+
|
3
|
+
module FileWatch
|
4
|
+
class ObservingTail
|
5
|
+
include TailBase
|
6
|
+
public
|
7
|
+
|
8
|
+
class NullListener
|
9
|
+
def initialize(path) @path = path; end
|
10
|
+
def accept(line) end
|
11
|
+
def deleted() end
|
12
|
+
def created() end
|
13
|
+
def error() end
|
14
|
+
def eof() end
|
15
|
+
def timed_out() end
|
16
|
+
end
|
17
|
+
|
18
|
+
class NullObserver
|
19
|
+
def listener_for(path) NullListener.new(path); end
|
20
|
+
end
|
21
|
+
|
22
|
+
def subscribe(observer = NullObserver.new)
|
23
|
+
@watch.subscribe(@opts[:stat_interval],
|
24
|
+
@opts[:discover_interval]) do |event, path|
|
25
|
+
listener = observer.listener_for(path)
|
26
|
+
case event
|
27
|
+
when :create, :create_initial
|
28
|
+
if @files.member?(path)
|
29
|
+
@logger.debug? && @logger.debug("#{event} for #{path}: already exists in @files")
|
30
|
+
next
|
31
|
+
end
|
32
|
+
if _open_file(path, event)
|
33
|
+
listener.created
|
34
|
+
observe_read_file(path, listener)
|
35
|
+
end
|
36
|
+
when :modify
|
37
|
+
if !@files.member?(path)
|
38
|
+
@logger.debug? && @logger.debug(":modify for #{path}, does not exist in @files")
|
39
|
+
if _open_file(path, event)
|
40
|
+
observe_read_file(path, listener)
|
41
|
+
end
|
42
|
+
else
|
43
|
+
observe_read_file(path, listener)
|
44
|
+
end
|
45
|
+
when :delete
|
46
|
+
@logger.debug? && @logger.debug(":delete for #{path}, deleted from @files")
|
47
|
+
if @files[path]
|
48
|
+
observe_read_file(path, listener)
|
49
|
+
@files[path].close
|
50
|
+
end
|
51
|
+
listener.deleted
|
52
|
+
@files.delete(path)
|
53
|
+
@statcache.delete(path)
|
54
|
+
when :timeout
|
55
|
+
@logger.debug? && @logger.debug(":timeout for #{path}, deleted from @files")
|
56
|
+
if (deleted = @files.delete(path))
|
57
|
+
deleted.close
|
58
|
+
end
|
59
|
+
listener.timed_out
|
60
|
+
@statcache.delete(path)
|
61
|
+
else
|
62
|
+
@logger.warn("unknown event type #{event} for #{path}")
|
63
|
+
end
|
64
|
+
end # @watch.subscribe
|
65
|
+
end # def subscribe
|
66
|
+
|
67
|
+
private
|
68
|
+
def observe_read_file(path, listener)
|
69
|
+
@buffers[path] ||= FileWatch::BufferedTokenizer.new(@opts[:delimiter])
|
70
|
+
delimiter_byte_size = @opts[:delimiter].bytesize
|
71
|
+
changed = false
|
72
|
+
loop do
|
73
|
+
begin
|
74
|
+
data = @files[path].sysread(32768)
|
75
|
+
changed = true
|
76
|
+
@buffers[path].extract(data).each do |line|
|
77
|
+
listener.accept(line)
|
78
|
+
@sincedb[@statcache[path]] += (line.bytesize + delimiter_byte_size)
|
79
|
+
end
|
80
|
+
rescue EOFError
|
81
|
+
listener.eof
|
82
|
+
break
|
83
|
+
rescue Errno::EWOULDBLOCK, Errno::EINTR
|
84
|
+
listener.error
|
85
|
+
break
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
if changed
|
90
|
+
now = Time.now.to_i
|
91
|
+
delta = now - @sincedb_last_write
|
92
|
+
if delta >= @opts[:sincedb_write_interval]
|
93
|
+
@logger.debug? && @logger.debug("writing sincedb (delta since last write = #{delta})")
|
94
|
+
_sincedb_write
|
95
|
+
@sincedb_last_write = now
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end # def _read_file
|
99
|
+
end
|
100
|
+
end # module FileWatch
|
data/lib/filewatch/tail.rb
CHANGED
@@ -1,272 +1,23 @@
|
|
1
|
-
require "filewatch/
|
2
|
-
require "filewatch/
|
3
|
-
require "
|
4
|
-
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
5
|
-
require "filewatch/winhelper"
|
6
|
-
end
|
7
|
-
require "logger"
|
8
|
-
require "rbconfig"
|
9
|
-
|
10
|
-
include Java if defined? JRUBY_VERSION
|
11
|
-
require "JRubyFileExtension.jar" if defined? JRUBY_VERSION
|
1
|
+
require "filewatch/yielding_tail"
|
2
|
+
require "filewatch/observing_tail"
|
3
|
+
require "forwardable"
|
12
4
|
|
13
5
|
module FileWatch
|
14
6
|
class Tail
|
15
|
-
|
16
|
-
OPEN_WARN_INTERVAL = ENV["FILEWATCH_OPEN_WARN_INTERVAL"] ?
|
17
|
-
ENV["FILEWATCH_OPEN_WARN_INTERVAL"].to_i : 300
|
18
|
-
|
19
|
-
attr_accessor :logger
|
20
|
-
|
21
|
-
class NoSinceDBPathGiven < StandardError; end
|
22
|
-
|
23
|
-
public
|
24
|
-
def initialize(opts={})
|
25
|
-
@iswindows = ((RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/) != nil)
|
26
|
-
|
27
|
-
if opts[:logger]
|
28
|
-
@logger = opts[:logger]
|
29
|
-
else
|
30
|
-
@logger = Logger.new(STDERR)
|
31
|
-
@logger.level = Logger::INFO
|
32
|
-
end
|
33
|
-
@files = {}
|
34
|
-
@lastwarn = Hash.new { |h, k| h[k] = 0 }
|
35
|
-
@buffers = {}
|
36
|
-
@watch = FileWatch::Watch.new
|
37
|
-
@watch.logger = @logger
|
38
|
-
@sincedb = {}
|
39
|
-
@sincedb_last_write = 0
|
40
|
-
@statcache = {}
|
41
|
-
@opts = {
|
42
|
-
:sincedb_write_interval => 10,
|
43
|
-
:stat_interval => 1,
|
44
|
-
:discover_interval => 5,
|
45
|
-
:exclude => [],
|
46
|
-
:start_new_files_at => :end,
|
47
|
-
:delimiter => "\n"
|
48
|
-
}.merge(opts)
|
49
|
-
if !@opts.include?(:sincedb_path)
|
50
|
-
@opts[:sincedb_path] = File.join(ENV["HOME"], ".sincedb") if ENV.include?("HOME")
|
51
|
-
@opts[:sincedb_path] = ENV["SINCEDB_PATH"] if ENV.include?("SINCEDB_PATH")
|
52
|
-
end
|
53
|
-
if !@opts.include?(:sincedb_path)
|
54
|
-
raise NoSinceDBPathGiven.new("No HOME or SINCEDB_PATH set in environment. I need one of these set so I can keep track of the files I am following.")
|
55
|
-
end
|
56
|
-
@watch.exclude(@opts[:exclude])
|
57
|
-
|
58
|
-
_sincedb_open
|
59
|
-
end # def initialize
|
60
|
-
|
61
|
-
public
|
62
|
-
def logger=(logger)
|
63
|
-
@logger = logger
|
64
|
-
@watch.logger = logger
|
65
|
-
end # def logger=
|
66
|
-
|
67
|
-
public
|
68
|
-
def tail(path)
|
69
|
-
@watch.watch(path)
|
70
|
-
end # def tail
|
7
|
+
extend Forwardable
|
71
8
|
|
72
|
-
|
73
|
-
def subscribe(&block)
|
74
|
-
# subscribe(stat_interval = 1, discover_interval = 5, &block)
|
75
|
-
@watch.subscribe(@opts[:stat_interval],
|
76
|
-
@opts[:discover_interval]) do |event, path|
|
77
|
-
case event
|
78
|
-
when :create, :create_initial
|
79
|
-
if @files.member?(path)
|
80
|
-
@logger.debug? && @logger.debug("#{event} for #{path}: already exists in @files")
|
81
|
-
next
|
82
|
-
end
|
83
|
-
if _open_file(path, event)
|
84
|
-
_read_file(path, &block)
|
85
|
-
end
|
86
|
-
when :modify
|
87
|
-
if !@files.member?(path)
|
88
|
-
@logger.debug? && @logger.debug(":modify for #{path}, does not exist in @files")
|
89
|
-
if _open_file(path, event)
|
90
|
-
_read_file(path, &block)
|
91
|
-
end
|
92
|
-
else
|
93
|
-
_read_file(path, &block)
|
94
|
-
end
|
95
|
-
when :delete
|
96
|
-
@logger.debug? && @logger.debug(":delete for #{path}, deleted from @files")
|
97
|
-
if @files[path]
|
98
|
-
_read_file(path, &block)
|
99
|
-
@files[path].close
|
100
|
-
end
|
101
|
-
@files.delete(path)
|
102
|
-
@statcache.delete(path)
|
103
|
-
else
|
104
|
-
@logger.warn("unknown event type #{event} for #{path}")
|
105
|
-
end
|
106
|
-
end # @watch.subscribe
|
107
|
-
end # def subscribe
|
9
|
+
def_delegators :@target, :tail, :logger=, :subscribe, :sincedb_record_uid, :sincedb_write, :quit, :close_file
|
108
10
|
|
109
|
-
|
110
|
-
def sincedb_record_uid(path, stat)
|
111
|
-
inode = @watch.inode(path,stat)
|
112
|
-
@statcache[path] = inode
|
113
|
-
return inode
|
114
|
-
end # def sincedb_record_uid
|
11
|
+
attr_writer :target
|
115
12
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
begin
|
120
|
-
if @iswindows && defined? JRUBY_VERSION
|
121
|
-
@files[path] = Java::RubyFileExt::getRubyFile(path)
|
122
|
-
else
|
123
|
-
@files[path] = File.open(path)
|
124
|
-
end
|
125
|
-
rescue
|
126
|
-
# don't emit this message too often. if a file that we can't
|
127
|
-
# read is changing a lot, we'll try to open it more often,
|
128
|
-
# and might be spammy.
|
129
|
-
now = Time.now.to_i
|
130
|
-
if now - @lastwarn[path] > OPEN_WARN_INTERVAL
|
131
|
-
@logger.warn("failed to open #{path}: #{$!}")
|
132
|
-
@lastwarn[path] = now
|
133
|
-
else
|
134
|
-
@logger.debug? && @logger.debug("(warn supressed) failed to open #{path}: #{$!}")
|
135
|
-
end
|
136
|
-
@files.delete(path)
|
137
|
-
return false
|
13
|
+
def self.new_observing(opts = {})
|
14
|
+
new.tap do |instance|
|
15
|
+
instance.target = ObservingTail.new(opts)
|
138
16
|
end
|
139
|
-
|
140
|
-
stat = File::Stat.new(path)
|
141
|
-
sincedb_record_uid = sincedb_record_uid(path, stat)
|
142
|
-
|
143
|
-
if @sincedb.member?(sincedb_record_uid)
|
144
|
-
last_size = @sincedb[sincedb_record_uid]
|
145
|
-
@logger.debug? && @logger.debug("#{path}: sincedb last value #{@sincedb[sincedb_record_uid]}, cur size #{stat.size}")
|
146
|
-
if last_size <= stat.size
|
147
|
-
@logger.debug? && @logger.debug("#{path}: sincedb: seeking to #{last_size}")
|
148
|
-
@files[path].sysseek(last_size, IO::SEEK_SET)
|
149
|
-
else
|
150
|
-
@logger.debug? && @logger.debug("#{path}: last value size is greater than current value, starting over")
|
151
|
-
@sincedb[sincedb_record_uid] = 0
|
152
|
-
end
|
153
|
-
elsif event == :create_initial && @files[path]
|
154
|
-
if @opts[:start_new_files_at] == :beginning
|
155
|
-
@logger.debug? && @logger.debug("#{path}: initial create, no sincedb, seeking to beginning of file")
|
156
|
-
@files[path].sysseek(0, IO::SEEK_SET)
|
157
|
-
@sincedb[sincedb_record_uid] = 0
|
158
|
-
else
|
159
|
-
# seek to end
|
160
|
-
@logger.debug? && @logger.debug("#{path}: initial create, no sincedb, seeking to end #{stat.size}")
|
161
|
-
@files[path].sysseek(stat.size, IO::SEEK_SET)
|
162
|
-
@sincedb[sincedb_record_uid] = stat.size
|
163
|
-
end
|
164
|
-
elsif event == :create && @files[path]
|
165
|
-
@sincedb[sincedb_record_uid] = 0
|
166
|
-
else
|
167
|
-
@logger.debug? && @logger.debug("#{path}: staying at position 0, no sincedb")
|
168
|
-
end
|
169
|
-
|
170
|
-
return true
|
171
|
-
end # def _open_file
|
172
|
-
|
173
|
-
private
|
174
|
-
def _read_file(path, &block)
|
175
|
-
@buffers[path] ||= FileWatch::BufferedTokenizer.new(@opts[:delimiter])
|
176
|
-
delimiter_byte_size = @opts[:delimiter].bytesize
|
177
|
-
changed = false
|
178
|
-
loop do
|
179
|
-
begin
|
180
|
-
data = @files[path].sysread(32768)
|
181
|
-
changed = true
|
182
|
-
@buffers[path].extract(data).each do |line|
|
183
|
-
yield(path, line)
|
184
|
-
@sincedb[@statcache[path]] += (line.bytesize + delimiter_byte_size)
|
185
|
-
end
|
186
|
-
rescue Errno::EWOULDBLOCK, Errno::EINTR, EOFError
|
187
|
-
break
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
if changed
|
192
|
-
now = Time.now.to_i
|
193
|
-
delta = now - @sincedb_last_write
|
194
|
-
if delta >= @opts[:sincedb_write_interval]
|
195
|
-
@logger.debug? && @logger.debug("writing sincedb (delta since last write = #{delta})")
|
196
|
-
_sincedb_write
|
197
|
-
@sincedb_last_write = now
|
198
|
-
end
|
199
|
-
end
|
200
|
-
end # def _read_file
|
201
|
-
|
202
|
-
public
|
203
|
-
def sincedb_write(reason=nil)
|
204
|
-
@logger.debug? && @logger.debug("caller requested sincedb write (#{reason})")
|
205
|
-
_sincedb_write
|
206
17
|
end
|
207
18
|
|
208
|
-
|
209
|
-
|
210
|
-
path = @opts[:sincedb_path]
|
211
|
-
begin
|
212
|
-
db = File.open(path)
|
213
|
-
rescue
|
214
|
-
#No existing sincedb to load
|
215
|
-
@logger.debug? && @logger.debug("_sincedb_open: #{path}: #{$!}")
|
216
|
-
return
|
217
|
-
end
|
218
|
-
|
219
|
-
@logger.debug? && @logger.debug("_sincedb_open: reading from #{path}")
|
220
|
-
db.each do |line|
|
221
|
-
ino, dev_major, dev_minor, pos = line.split(" ", 4)
|
222
|
-
sincedb_record_uid = [ino, dev_major.to_i, dev_minor.to_i]
|
223
|
-
@logger.debug? && @logger.debug("_sincedb_open: setting #{sincedb_record_uid.inspect} to #{pos.to_i}")
|
224
|
-
@sincedb[sincedb_record_uid] = pos.to_i
|
225
|
-
end
|
226
|
-
db.close
|
227
|
-
end # def _sincedb_open
|
228
|
-
|
229
|
-
private
|
230
|
-
def _sincedb_write
|
231
|
-
path = @opts[:sincedb_path]
|
232
|
-
if @iswindows || File.device?(path)
|
233
|
-
IO.write(path, serialize_sincedb, 0)
|
234
|
-
else
|
235
|
-
File.atomic_write(path) {|file| file.write(serialize_sincedb) }
|
236
|
-
end
|
237
|
-
end # def _sincedb_write
|
238
|
-
|
239
|
-
public
|
240
|
-
# quit is a sort-of finalizer,
|
241
|
-
# it should be called for clean up
|
242
|
-
# before the instance is disposed of.
|
243
|
-
def quit
|
244
|
-
_sincedb_write
|
245
|
-
@watch.quit
|
246
|
-
@files.each {|path, file| file.close }
|
247
|
-
@files.clear
|
248
|
-
end # def quit
|
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
|
19
|
+
def initialize(opts = {})
|
20
|
+
@target = YieldingTail.new(opts)
|
263
21
|
end
|
264
|
-
|
265
|
-
|
266
|
-
def serialize_sincedb
|
267
|
-
@sincedb.map do |inode, pos|
|
268
|
-
[inode, pos].flatten.join(" ")
|
269
|
-
end.join("\n") + "\n"
|
270
|
-
end
|
271
|
-
end # class Tail
|
272
|
-
end # module FileWatch
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
require "filewatch/helper"
|
2
|
+
require "filewatch/buftok"
|
3
|
+
require "filewatch/watch"
|
4
|
+
|
5
|
+
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
6
|
+
require "filewatch/winhelper"
|
7
|
+
end
|
8
|
+
require "logger"
|
9
|
+
require "rbconfig"
|
10
|
+
|
11
|
+
include Java if defined? JRUBY_VERSION
|
12
|
+
require "JRubyFileExtension.jar" if defined? JRUBY_VERSION
|
13
|
+
|
14
|
+
module FileWatch
|
15
|
+
module TailBase
|
16
|
+
# how often (in seconds) we @logger.warn a failed file open, per path.
|
17
|
+
OPEN_WARN_INTERVAL = ENV["FILEWATCH_OPEN_WARN_INTERVAL"] ?
|
18
|
+
ENV["FILEWATCH_OPEN_WARN_INTERVAL"].to_i : 300
|
19
|
+
|
20
|
+
attr_reader :logger
|
21
|
+
|
22
|
+
class NoSinceDBPathGiven < StandardError; end
|
23
|
+
|
24
|
+
public
|
25
|
+
def initialize(opts={})
|
26
|
+
@iswindows = ((RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/) != nil)
|
27
|
+
|
28
|
+
if opts[:logger]
|
29
|
+
@logger = opts[:logger]
|
30
|
+
else
|
31
|
+
@logger = Logger.new(STDERR)
|
32
|
+
@logger.level = Logger::INFO
|
33
|
+
end
|
34
|
+
@files = {}
|
35
|
+
@lastwarn = Hash.new { |h, k| h[k] = 0 }
|
36
|
+
@buffers = {}
|
37
|
+
@watch = FileWatch::Watch.new
|
38
|
+
@watch.logger = @logger
|
39
|
+
@sincedb = {}
|
40
|
+
@sincedb_last_write = 0
|
41
|
+
@statcache = {}
|
42
|
+
@opts = {
|
43
|
+
:sincedb_write_interval => 10,
|
44
|
+
:stat_interval => 1,
|
45
|
+
:discover_interval => 5,
|
46
|
+
:exclude => [],
|
47
|
+
:start_new_files_at => :end,
|
48
|
+
# :ignore_after => 24 * 60 * 60,
|
49
|
+
:delimiter => "\n"
|
50
|
+
}.merge(opts)
|
51
|
+
if !@opts.include?(:sincedb_path)
|
52
|
+
@opts[:sincedb_path] = File.join(ENV["HOME"], ".sincedb") if ENV.include?("HOME")
|
53
|
+
@opts[:sincedb_path] = ENV["SINCEDB_PATH"] if ENV.include?("SINCEDB_PATH")
|
54
|
+
end
|
55
|
+
if !@opts.include?(:sincedb_path)
|
56
|
+
raise NoSinceDBPathGiven.new("No HOME or SINCEDB_PATH set in environment. I need one of these set so I can keep track of the files I am following.")
|
57
|
+
end
|
58
|
+
@watch.exclude(@opts[:exclude])
|
59
|
+
@watch.ignore_after = @opts[:ignore_after]
|
60
|
+
|
61
|
+
_sincedb_open
|
62
|
+
end # def initialize
|
63
|
+
|
64
|
+
public
|
65
|
+
def logger=(logger)
|
66
|
+
@logger = logger
|
67
|
+
@watch.logger = logger
|
68
|
+
end # def logger=
|
69
|
+
|
70
|
+
public
|
71
|
+
def tail(path)
|
72
|
+
@watch.watch(path)
|
73
|
+
end # def tail
|
74
|
+
|
75
|
+
public
|
76
|
+
def sincedb_record_uid(path, stat)
|
77
|
+
inode = @watch.inode(path,stat)
|
78
|
+
@statcache[path] = inode
|
79
|
+
return inode
|
80
|
+
end # def sincedb_record_uid
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def file_expired?(stat)
|
85
|
+
return false if @opts[:ignore_after].nil?
|
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_after]
|
90
|
+
end
|
91
|
+
|
92
|
+
def _open_file(path, event)
|
93
|
+
@logger.debug? && @logger.debug("_open_file: #{path}: opening")
|
94
|
+
begin
|
95
|
+
if @iswindows && defined? JRUBY_VERSION
|
96
|
+
@files[path] = Java::RubyFileExt::getRubyFile(path)
|
97
|
+
else
|
98
|
+
@files[path] = File.open(path)
|
99
|
+
end
|
100
|
+
rescue
|
101
|
+
# don't emit this message too often. if a file that we can't
|
102
|
+
# read is changing a lot, we'll try to open it more often,
|
103
|
+
# and might be spammy.
|
104
|
+
now = Time.now.to_i
|
105
|
+
if now - @lastwarn[path] > OPEN_WARN_INTERVAL
|
106
|
+
@logger.warn("failed to open #{path}: #{$!}")
|
107
|
+
@lastwarn[path] = now
|
108
|
+
else
|
109
|
+
@logger.debug? && @logger.debug("(warn supressed) failed to open #{path}: #{$!}")
|
110
|
+
end
|
111
|
+
@files.delete(path)
|
112
|
+
return false
|
113
|
+
end
|
114
|
+
|
115
|
+
stat = File::Stat.new(path)
|
116
|
+
sincedb_record_uid = sincedb_record_uid(path, stat)
|
117
|
+
|
118
|
+
expired_based_size = file_expired?(stat) ? stat.size : 0
|
119
|
+
|
120
|
+
if @sincedb.member?(sincedb_record_uid)
|
121
|
+
last_size = @sincedb[sincedb_record_uid]
|
122
|
+
@logger.debug? && @logger.debug("#{path}: sincedb last value #{@sincedb[sincedb_record_uid]}, cur size #{stat.size}")
|
123
|
+
if last_size <= stat.size
|
124
|
+
@logger.debug? && @logger.debug("#{path}: sincedb: seeking to #{last_size}")
|
125
|
+
@files[path].sysseek(last_size, IO::SEEK_SET)
|
126
|
+
else
|
127
|
+
@logger.debug? && @logger.debug("#{path}: last value size is greater than current value, starting over")
|
128
|
+
@sincedb[sincedb_record_uid] = 0
|
129
|
+
end
|
130
|
+
elsif event == :create_initial && @files[path]
|
131
|
+
if @opts[:start_new_files_at] == :beginning
|
132
|
+
@logger.debug? && @logger.debug("#{path}: initial create, no sincedb, seeking to beginning of file")
|
133
|
+
@files[path].sysseek(expired_based_size, IO::SEEK_SET)
|
134
|
+
@sincedb[sincedb_record_uid] = expired_based_size
|
135
|
+
else
|
136
|
+
# seek to end
|
137
|
+
@logger.debug? && @logger.debug("#{path}: initial create, no sincedb, seeking to end #{stat.size}")
|
138
|
+
@files[path].sysseek(stat.size, IO::SEEK_SET)
|
139
|
+
@sincedb[sincedb_record_uid] = stat.size
|
140
|
+
end
|
141
|
+
elsif event == :create && @files[path]
|
142
|
+
@sincedb[sincedb_record_uid] = expired_based_size
|
143
|
+
else
|
144
|
+
@logger.debug? && @logger.debug("#{path}: staying at position 0, no sincedb")
|
145
|
+
end
|
146
|
+
|
147
|
+
return true
|
148
|
+
end # def _open_file
|
149
|
+
|
150
|
+
public
|
151
|
+
def sincedb_write(reason=nil)
|
152
|
+
@logger.debug? && @logger.debug("caller requested sincedb write (#{reason})")
|
153
|
+
_sincedb_write
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
def _sincedb_open
|
158
|
+
path = @opts[:sincedb_path]
|
159
|
+
begin
|
160
|
+
db = File.open(path)
|
161
|
+
rescue
|
162
|
+
#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
|
173
|
+
end
|
174
|
+
db.close
|
175
|
+
end # def _sincedb_open
|
176
|
+
|
177
|
+
private
|
178
|
+
def _sincedb_write
|
179
|
+
path = @opts[:sincedb_path]
|
180
|
+
if @iswindows || File.device?(path)
|
181
|
+
IO.write(path, serialize_sincedb, 0)
|
182
|
+
else
|
183
|
+
File.atomic_write(path) {|file| file.write(serialize_sincedb) }
|
184
|
+
end
|
185
|
+
end # def _sincedb_write
|
186
|
+
|
187
|
+
public
|
188
|
+
# quit is a sort-of finalizer,
|
189
|
+
# it should be called for clean up
|
190
|
+
# before the instance is disposed of.
|
191
|
+
def quit
|
192
|
+
_sincedb_write
|
193
|
+
@watch.quit
|
194
|
+
@files.each {|path, file| file.close }
|
195
|
+
@files.clear
|
196
|
+
end # def quit
|
197
|
+
|
198
|
+
public
|
199
|
+
# close_file(path) is to be used by external code
|
200
|
+
# when it knows that it is completely done with a file.
|
201
|
+
# Other files or folders may still be being watched.
|
202
|
+
# Caution, once unwatched, a file can't be watched again
|
203
|
+
# unless a new instance of this class begins watching again.
|
204
|
+
# The sysadmin should rename, move or delete the file.
|
205
|
+
def close_file(path)
|
206
|
+
@watch.unwatch(path)
|
207
|
+
file = @files.delete(path)
|
208
|
+
return if file.nil?
|
209
|
+
_sincedb_write
|
210
|
+
file.close
|
211
|
+
end
|
212
|
+
|
213
|
+
private
|
214
|
+
def serialize_sincedb
|
215
|
+
@sincedb.map do |inode, pos|
|
216
|
+
[inode, pos].flatten.join(" ")
|
217
|
+
end.join("\n") + "\n"
|
218
|
+
end
|
219
|
+
end # module TailBase
|
220
|
+
end # module FileWatch
|