filewatch 0.6.7 → 0.6.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3c7e0e544aadf722674dd85b5ba0441dce6ecfc4
4
- data.tar.gz: 4f6d362df8d9d5f105bcbf12f576e0cf8db69966
3
+ metadata.gz: d9bf179bb78fa086471f144cc90404a616076d09
4
+ data.tar.gz: e0579d790be4bc6a400f4db10097cf610fbc219a
5
5
  SHA512:
6
- metadata.gz: 5fa00a10ea8b093e6484aabbd9f653a7d2f06b97dafdcf2a2f2795f750cf2c3f0d8c13b19f2c41809e5284ed44316e7b448eda1499f7c9b817309a53a34d33c9
7
- data.tar.gz: 0c8f460f91f9830deb1b096c22facc1106ea99a73ec297ad41384787bfa29f5302760696411d6b2c3517bb9b3eaa226343a0895ac78eb298ee1a04309b16a51a
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
@@ -1,272 +1,23 @@
1
- require "filewatch/helper"
2
- require "filewatch/buftok"
3
- require "filewatch/watch"
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
- # how often (in seconds) we @logger.warn a failed file open, per path.
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
- public
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
- public
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
- private
117
- def _open_file(path, event)
118
- @logger.debug? && @logger.debug("_open_file: #{path}: opening")
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
- private
209
- def _sincedb_open
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
- private
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