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 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