filewatch 0.2.5 → 0.3.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.
@@ -1,3 +0,0 @@
1
- module FileWatch
2
- module Inotify ; end
3
- end
@@ -1,8 +0,0 @@
1
-
2
- # Ruby <= 1.8.6 doesn't have String#bytesize
3
- # FFI 1.0.7 wants it. Monkeypatch time.
4
- if !String.instance_methods.include?("bytesize")
5
- class String
6
- alias :bytesize :size
7
- end
8
- end
@@ -1,33 +0,0 @@
1
- require "rubygems"
2
- require "filewatch/namespace"
3
-
4
- class FileWatch::StringPipeIO
5
- def initialize
6
- @buffer = ""
7
- end # def initialize
8
-
9
- def write(string)
10
- @buffer += string
11
- end # def write
12
-
13
- def read(bytes=nil, nil_if_not_enough_data=false)
14
- return nil if @buffer == nil || @buffer.empty?
15
-
16
- if nil_if_not_enough_data && bytes > @buffer.size
17
- return nil
18
- end
19
-
20
- bytes = @buffer.size if bytes == nil
21
- data = @buffer[0 .. bytes]
22
- if bytes <= @buffer.length
23
- @buffer = @buffer[bytes .. -1]
24
- else
25
- @buffer = ""
26
- end
27
- return data
28
- end # def read
29
-
30
- def size
31
- return @buffer.size
32
- end
33
- end # class Inotify::StringPipeIO
@@ -1,244 +0,0 @@
1
- require "filewatch/namespace"
2
- require "filewatch/exception"
3
- require "filewatch/watchglob"
4
- require "logger"
5
-
6
- class FileWatch::TailGlob
7
- attr_accessor :logger
8
-
9
- public
10
- def initialize
11
- @watch = FileWatch::WatchGlob.new
12
-
13
- # hash of string path => File
14
- @files = {}
15
- @globoptions = {}
16
- @options = {}
17
-
18
- self.logger = Logger.new(STDERR)
19
- # hash of string path => action to take on EOF
20
- end # def initialize
21
-
22
- def logger=(logger)
23
- @logger = logger
24
- @watch.logger = logger
25
- end
26
-
27
- # Watch a path glob.
28
- #
29
- # Options is a hash of:
30
- # :exclude => array of globs to ignore.
31
- public
32
- def tail(glob, options={}, &block)
33
- what_to_watch = [ :create, :modify, :delete_self, :move_self ]
34
-
35
- # Setup a callback specific to tihs tail call for handling
36
- # new files found; so we can attach options to each new file.
37
- callback_set_options = lambda do |path|
38
- @options[path] = options
39
- end
40
-
41
- # Save glob options
42
- @globoptions[glob] = options
43
-
44
- # callbacks can be an array of functions to call.
45
- callbacks = [ callback_set_options ]
46
- callbacks << block if block_given?
47
- @globoptions[glob][:new_follow_callback] = callbacks
48
-
49
- @watch.watch(glob, *what_to_watch) do |path|
50
- # Save per-path options
51
- callback_set_options.call(path)
52
-
53
- # for each file found by the glob, open it.
54
- follow_file(path, :end)
55
- end # @watch.watch
56
- end # def watch
57
-
58
- private
59
- def follow_file(path, seek=:end)
60
- # Don't follow things that aren't files.
61
- if !File.file?(path)
62
- @logger.info "Skipping follow on #{path}, File.file? == false"
63
- return
64
- end
65
-
66
- options = @options[path] || {}
67
- if options.include?(:exclude)
68
- options[:exclude].each do |exclusion|
69
- if File.fnmatch?(exclusion, path)
70
- @logger.info "Skipping #{path.inspect}, matches exclusion #{exclusion.inspect}"
71
- return
72
- end
73
- end
74
- end
75
-
76
- if options.include?(:new_follow_callback)
77
- options[:new_follow_callback].each do |callback|
78
- #puts "Callback: #{callback.inspect}"
79
- callback.call(path)
80
- end
81
- end
82
-
83
- close(path) if following?(path)
84
- @files[path] = File.new(path, "r")
85
-
86
- # TODO(sissel): Support 'since'-like support.
87
- case seek
88
- when :end; @files[path].sysseek(0, IO::SEEK_END)
89
- when :beginning; # nothing
90
- else
91
- if seek.is_a?(Numeric)
92
- # 'seek' could be a number that is an offset from
93
- # the start of the file. We should seek to that point.
94
- @files[path].sysseek(seek, IO::SEEK_SET)
95
- end # seek.is_a?(Numeric)
96
- end # case seek
97
- end # def follow_file
98
-
99
- public
100
- def subscribe(handler=nil, &block)
101
- # TODO(sissel): Add handler support.
102
- @watch.subscribe do |event|
103
- path = event.name
104
-
105
- p path => event.actions if $DEBUG
106
- event.actions.each do |action|
107
- method = "file_action_#{action}".to_sym
108
- if respond_to?(method)
109
- send(method, path, event, &block)
110
- else
111
- $stderr.puts "Unsupported method #{self.class.name}##{method}"
112
- end
113
- end
114
- end # @watch.subscribe
115
- end # def subscribe
116
-
117
- protected
118
- def file_action_modify(path, event, &block)
119
- file = @files[path]
120
- # Check if this path is in the exclude list.
121
- # TODO(sissel): Is this check sufficient?
122
- if file.nil?
123
- @logger.info "Ignoring modify on '#{path}' - it's probably ignored'"
124
- return
125
- end
126
-
127
- # Read until EOF, emitting each chunk read.
128
- loop do
129
- begin
130
- data = file.sysread(4096)
131
- yield event.name, data
132
- rescue EOFError
133
- check_for_truncation_or_deletion(path, event, &block)
134
- break
135
- end
136
- end # loop
137
- end # def file_action_modify
138
-
139
- protected
140
- def check_for_truncation_or_deletion(path, event, &block)
141
- file = @files[path]
142
- pos = file.sysseek(0, IO::SEEK_CUR)
143
- #puts "EOF(#{path}), pos: #{pos}"
144
-
145
- # Truncation is determined by comparing the current read position in the
146
- # file against the size of the file. If the file shrank, than we should
147
- # assume truncation and seek to the beginning.
148
- begin
149
- stat = file.stat
150
- #p stat.size => pos
151
- if stat.size < pos
152
- # Truncated. Seek to beginning and read.
153
- file.sysseek(0, IO::SEEK_SET)
154
- file_action_modify(path, event, &block)
155
- end
156
- rescue Errno::ENOENT
157
- # File was deleted or renamed. Stop following it.
158
- close(path)
159
- end
160
- end # def follow_file
161
-
162
- protected
163
- def reopen(path)
164
- close(path)
165
- follow_file(path, :beginning)
166
- end # def reopen
167
-
168
- protected
169
- def close(path)
170
- if following?(path)
171
- p :CLOSE => path if $DEBUG
172
- @files[path].close rescue nil
173
- @files.delete(path)
174
- end
175
- return nil
176
- end # def close
177
-
178
- protected
179
- def file_action_create(path, event, &block)
180
- if following?(path)
181
- # TODO(sissel): If we are watching this file already, schedule it to be
182
- # opened the next time we hit EOF on the current file descriptor for the
183
- # same file. Maybe? Or is reopening now fine?
184
- #
185
- reopen(path)
186
-
187
- # Schedule a reopen at EOF
188
- #@eof_actions[path] = :reopen
189
- else
190
- # If we are not yet watching this file, watch it.
191
- # Make sure this file matches a known glob
192
- if @globoptions.find { |glob, opts| File.fnmatch?(glob, path) }
193
- follow_file(path, :beginning)
194
- # Then read all of the data so far since this is a new file.
195
- file_action_modify(path, event, &block)
196
- end
197
- end
198
- end # def file_action_create
199
-
200
- # This method is invoked when a file we are tailing is deleted.
201
- protected
202
- def file_action_delete_self(path, event, &block)
203
- close(path)
204
- end # def file_action_delete_self
205
-
206
- # This method is invoked when a file we are tailing is moved.
207
- def file_action_move_self(path, event, &block)
208
- # File renames are assumed to be rotations, so we close.
209
- # if this file is not open, this close has no effect.
210
- close(event.old_name)
211
- end
212
-
213
- # Directory event; file in this dir was renamed.
214
- protected
215
- def file_action_moved_to(path, event, &block)
216
- # TODO(sissel): ignore, maybe call close for good measure?
217
- end
218
-
219
- protected
220
- def file_action_moved_from(path, event, &block)
221
- # ignore
222
- end # def file_action_moved_from
223
-
224
- protected
225
- def file_action_move(path, event, &block)
226
- # ignore
227
- end # def file_action_move
228
-
229
- protected
230
- def file_action_move(path, event, &block)
231
- # ignore
232
- end # def file_action_move
233
-
234
- protected
235
- def file_action_delete(path, event, &block)
236
- # ignore
237
- end # def file_action_delete
238
-
239
- # Returns true if we are currently following the file at the given path.
240
- public
241
- def following?(path)
242
- return @files.include?(path)
243
- end # def following
244
- end # class FileWatch::Tail
@@ -1,83 +0,0 @@
1
- require "filewatch/namespace"
2
- require "filewatch/exception"
3
- require "filewatch/watch"
4
-
5
- class FileWatch::WatchGlob
6
- attr_accessor :logger
7
-
8
- # This class exists to wrap inotify, kqueue, periodic polling, etc,
9
- # to provide you with a way to watch files and directories.
10
- #
11
- # For now, it only supports inotify.
12
- def initialize
13
- @watch = FileWatch::Watch.new
14
- @globdirs = []
15
- @globs = []
16
- @logger = Logger.new(STDERR)
17
- end
18
-
19
- public
20
- def watch(glob, *what_to_watch, &block)
21
- @globs << [glob, what_to_watch]
22
-
23
- watching = []
24
- errors = []
25
- paths = Dir.glob(glob)
26
- paths.each do |path|
27
- begin
28
- next if watching.include?(path)
29
- @logger.info("Watching #{path}")
30
- @watch.watch(path, :create, :delete, :modify, *what_to_watch)
31
- watching << path
32
-
33
- # Yield each file found by glob to the block.
34
- # This allows initialization on new files found at start.
35
- yield path if block_given?
36
- rescue FileWatch::Exception => e
37
- @logger.info("Failed starting watch on #{path} - #{e}")
38
- errors << e
39
- end
40
- end
41
-
42
- # Go through the glob and look for paths leading into a '*'
43
- splitpath = glob.split(File::SEPARATOR)
44
- splitpath.each_with_index do |part, i|
45
- current = File.join(splitpath[0 .. i])
46
- current = "/" if current.empty?
47
- next if watching.include?(current)
48
- # TODO(sissel): Do better glob detection
49
- if part.include?("*")
50
- globprefix = File.join(splitpath[0 ... i])
51
- Dir.glob(globprefix).each do |path|
52
- next if watching.include?(path)
53
- @logger.info("Watching dir: #{path.inspect}")
54
- @watch.watch(path, :create, :moved_to, :moved_from)
55
- @globdirs << path
56
- end # Dir.glob
57
- end # if part.include?("*")
58
- end # splitpath.each_with_index
59
- end # def watch
60
-
61
- # TODO(sissel): implement 'unwatch' or cancel?
62
-
63
- def subscribe(handler=nil, &block)
64
- @watch.subscribe do |event|
65
- # If this event is a directory event and the file matches a watched glob,
66
- # then it should be a new-file creation event. Watch the file.
67
- if event.type == :directory and event.actions.include?(:create) \
68
- and @globdirs.include?(File.dirname(event.name))
69
- glob, what = @globs.find { |glob, what| File.fnmatch?(glob, event.name) }
70
- if glob
71
- @watch.watch(event.name, *what)
72
- end
73
- end
74
-
75
- # Push the event to our callback.
76
- block.call event
77
- end
78
- end
79
-
80
- def each(&block)
81
- @inotify.each(&block)
82
- end # def each
83
- end # class FileWatch::Watch
@@ -1,21 +0,0 @@
1
- import org.apache.log4j.Logger;
2
- import org.apache.log4j.PropertyConfigurator;
3
-
4
- public class LogTest {
5
- private static Logger logger = Logger.getLogger(LogTest.class);
6
-
7
- public static void main(String[] args) {
8
- //BasicConfigurator.configure();
9
- PropertyConfigurator.configure("log4j.properties");
10
-
11
- int i = 0;
12
- while (true) {
13
- logger.info("Testing: " + i);
14
- i++;
15
- try {
16
- Thread.sleep(100);
17
- } catch (Exception e) {
18
- }
19
- }
20
- } /* public static void main(String[]) */
21
- } /* public class LogTest */
@@ -1,20 +0,0 @@
1
- log4j.rootLogger=INFO,stdout,rolling
2
- log4j.appender.stdout=org.apache.log4j.ConsoleAppender
3
- log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
4
- log4j.appender.stdout.layout.ConversionPattern=%5p %d{HH:mm:ss,SSS} %m%n
5
-
6
- log4j.appender.rolling=org.apache.log4j.RollingFileAppender
7
- log4j.appender.rolling.maxFileSize=1000
8
- log4j.appender.rolling.maxBackupIndex=10
9
- log4j.appender.rolling.layout=org.apache.log4j.PatternLayout
10
- log4j.appender.rolling.layout.ConversionPattern=%5p [%t] %d{ISO8601} %F (line %L) %m%n
11
- log4j.appender.rolling.File=logs/logtest.log
12
- log4j.appender.rolling.DatePattern=.yyyy-MM-dd.HH-mm-ss
13
-
14
- #log4j.appender.D=org.apache.log4j.DailyRollingFileAppender
15
- #log4j.appender.D.layout=org.apache.log4j.PatternLayout
16
- #log4j.appender.D.layout=org.apache.log4j.JSONLayout
17
- #log4j.appender.D.layout.ConversionPattern=%5p [%t] %d{ISO8601} %F (line %L) %m%n
18
- #log4j.appender.D.File=logs/logtest.log
19
- #log4j.appender.D.DatePattern=.yyyy-MM-dd.HH-mm-ss
20
-
@@ -1,5 +0,0 @@
1
- /home/jls/projects/ruby-filewatch/test/logrotate/*.log {
2
- rotate 5
3
- #compress
4
- size 100
5
- }