eventmachine-tail 0.2.20100516235116 → 0.2.20100517011408

Sign up to get free protection for your applications and to get access to all the features.
data/lib/em/filetail.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "rubygems" if __FILE__ == $0
4
3
  require "eventmachine"
5
4
  require "logger"
6
5
 
@@ -9,7 +8,7 @@ EventMachine.epoll if EventMachine.epoll?
9
8
  # Tail a file.
10
9
  #
11
10
  # Example
12
- # class Tailer < EventMachine::Tail
11
+ # class Tailer < EventMachine::FileTail
13
12
  # def receive_data(data)
14
13
  # puts "Got #{data.length} bytes"
15
14
  # end
@@ -24,18 +23,26 @@ EventMachine.epoll if EventMachine.epoll?
24
23
  # EM.run do
25
24
  # Tailer.new("/var/log/messages")
26
25
  # end
26
+ #
27
+ # See also: EventMachine::FileTail#receive_data
27
28
  class EventMachine::FileTail
29
+ # Maximum size to read at a time from a single file.
28
30
  CHUNKSIZE = 65536
29
- MAXSLEEP = 2
30
31
 
32
+ #
33
+ #MAXSLEEP = 2
34
+
35
+ # The path of the file being tailed
31
36
  attr_reader :path
32
37
 
33
38
  # Tail a file
34
39
  #
35
- # path is a string file path
36
- # startpos is an offset to start tailing the file at. If -1, start at end of
40
+ # * path is a string file path to tail
41
+ # * startpos is an offset to start tailing the file at. If -1, start at end of
37
42
  # file.
38
43
  #
44
+ # See also: EventMachine::file_tail
45
+ #
39
46
  public
40
47
  def initialize(path, startpos=-1)
41
48
  @path = path
@@ -51,7 +58,7 @@ class EventMachine::FileTail
51
58
  end
52
59
 
53
60
  open
54
- watch
61
+ watch { |what| notify(what) }
55
62
  if (startpos == -1)
56
63
  @file.sysseek(0, IO::SEEK_END)
57
64
  else
@@ -60,11 +67,33 @@ class EventMachine::FileTail
60
67
  end
61
68
  end # def initialize
62
69
 
63
- # notify is invoked by EventMachine when the file you are tailing
64
- # has been modified or otherwise needs to be acted on.
70
+ # This method is called when a tailed file has data read.
71
+ #
72
+ # * data - string data read from the file.
73
+ #
74
+ # If you want to read lines from your file, you should use BufferedTokenizer
75
+ # (which comes with EventMachine):
76
+ # class Tailer < EventMachine::FileTail
77
+ # def initialize(*args)
78
+ # super(*args)
79
+ # @buffer = BufferedTokenizer.new
80
+ # end
65
81
  #
66
- # You won't normally call this method.
82
+ # def receive_data(data)
83
+ # @buffer.extract(data).each do |line|
84
+ # # do something with 'line'
85
+ # end
86
+ # end
67
87
  public
88
+ def receive_data(data)
89
+ raise NotImplementedError.new("#{self.class.name}#receive_data is not "\
90
+ "implemented. Did you forget to implement this in your subclass or "\
91
+ "module?")
92
+ end # def receive_data
93
+
94
+ # notify is invoked when the file you are tailing has been modified or
95
+ # otherwise needs to be acted on.
96
+ private
68
97
  def notify(status)
69
98
  @logger.debug("#{status} on #{path}")
70
99
  if status == :modified
@@ -75,11 +104,7 @@ class EventMachine::FileTail
75
104
  end
76
105
  end
77
106
 
78
- public
79
- def receive_data(data)
80
- @logger.warn("Got #{data.length} bytes")
81
- end
82
-
107
+ # Open (or reopen, if necessary) our file and schedule a read.
83
108
  private
84
109
  def open
85
110
  @file.close if @file
@@ -95,11 +120,13 @@ class EventMachine::FileTail
95
120
  schedule_next_read
96
121
  end
97
122
 
123
+ # Watch our file.
98
124
  private
99
- def watch
100
- EventMachine::watch_file(@path, EventMachine::FileTail::FileWatcher, self)
125
+ def watch(&block)
126
+ EventMachine::watch_file(@path, EventMachine::FileTail::FileWatcher, block)
101
127
  end
102
128
 
129
+ # Schedule a read.
103
130
  private
104
131
  def schedule_next_read
105
132
  EventMachine::add_timer(@naptime) do
@@ -107,6 +134,7 @@ class EventMachine::FileTail
107
134
  end
108
135
  end
109
136
 
137
+ # Read CHUNKSIZE from our file and pass it to .receive_data()
110
138
  private
111
139
  def read
112
140
  begin
@@ -132,11 +160,12 @@ class EventMachine::FileTail
132
160
  #@logger.info("EOF. Naptime: #{@naptime}")
133
161
  #end
134
162
 
135
- # TODO(sissel): schedule an fstat instead of doing it now.
163
+ # TODO(sissel): should we schedule an fstat instead of doing it now?
136
164
  fstat = File.stat(@path)
137
165
  handle_fstat(fstat)
138
166
  end # def eof
139
167
 
168
+ # Handle fstat changes appropriately.
140
169
  private
141
170
  def handle_fstat(fstat)
142
171
  if (fstat.ino != @fstat.ino)
@@ -144,6 +173,7 @@ class EventMachine::FileTail
144
173
  elsif (fstat.rdev != @fstat.rdev)
145
174
  open # Reopen if the filesystem device changed
146
175
  elsif (fstat.size < @fstat.size)
176
+ # Schedule a read if the file size has changed
147
177
  @logger.info("File likely truncated... #{path}")
148
178
  @file.sysseek(0, IO::SEEK_SET)
149
179
  schedule_next_read
@@ -152,30 +182,32 @@ class EventMachine::FileTail
152
182
  end # def eof
153
183
  end # class EventMachine::FileTail
154
184
 
155
- # Internal usage only
185
+ # Internal usage only. This class is used by EventMachine::FileTail
186
+ # to watch files you are tailing.
187
+ #
188
+ # See also: EventMachine::FileTail#watch
156
189
  class EventMachine::FileTail::FileWatcher < EventMachine::FileWatch
157
- def initialize(filewatch)
158
- @filewatch = filewatch
190
+ def initialize(block)
159
191
  @logger = Logger.new(STDOUT)
160
192
  @logger.level = ($DEBUG and Logger::DEBUG or Logger::WARN)
161
- @logger.debug("Watching on #{filewatch.path}")
162
- end
193
+ @callback = block
194
+ end # def initialize
163
195
 
164
196
  def file_modified
165
- @filewatch.notify :modified
166
- end
197
+ @callback.call(:modified)
198
+ end # def file_modified
167
199
 
168
200
  def file_moved
169
- @filewatch.notify :moved
170
- end
201
+ @callback.call(:moved)
202
+ end # def file_moved
171
203
 
172
204
  def file_deleted
173
- @filewatch.notify :deleted
174
- end
205
+ @callback.call(:deleted)
206
+ end # def file_deleted
175
207
 
176
208
  def unbind
177
- @filewatch.notify :unbind
178
- end
209
+ @callback.call(:unbind)
210
+ end # def unbind
179
211
  end # class EventMachine::FileTail::FileWatch < EventMachine::FileWatch
180
212
 
181
213
  # Add EventMachine::file_tail
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "set"
3
+ require "em/filetail"
4
4
  require "eventmachine"
5
5
  require "logger"
6
- require "em/filetail"
6
+ require "set"
7
+
8
+ EventMachine.epoll if EventMachine.epoll?
7
9
 
8
10
  # A file glob pattern watcher for EventMachine.
9
11
  #
@@ -16,10 +18,22 @@ require "em/filetail"
16
18
  # This class will allow you to get notified whenever a file
17
19
  # is created or deleted that matches your glob.
18
20
  #
21
+ # If you are subclassing, here are the methods you should implement:
22
+ # file_found(path)
23
+ # file_deleted(path)
24
+ #
25
+ # See alsoe
26
+ # * EventMachine::watch_glob
27
+ # * EventMachine::FileGlobWatch#file_found
28
+ # * EventMachine::FileGlobWatch#file_deleted
19
29
  #
20
30
  class EventMachine::FileGlobWatch
21
- def initialize(pathglob, interval=60)
22
- @pathglob = pathglob
31
+ # Watch a glob
32
+ #
33
+ # * glob - a string path or glob, such as "/var/log/*.log"
34
+ # * interval - number of seconds between scanning the glob for changes
35
+ def initialize(glob, interval=60)
36
+ @glob = glob
23
37
  @files = Set.new
24
38
  @watches = Hash.new
25
39
  @logger = Logger.new(STDOUT)
@@ -36,13 +50,39 @@ class EventMachine::FileGlobWatch
36
50
  EM.add_periodic_timer(interval) do
37
51
  find_files
38
52
  end
39
- end
53
+ end # EM.next_tick
40
54
  end # def initialize
41
55
 
56
+ # This method is called when a new file is found
57
+ #
58
+ # * path - the string path of the file found
59
+ #
60
+ # You must implement this in your subclass or module for it
61
+ # to work with EventMachine::watch_glob
62
+ public
63
+ def file_found(path)
64
+ raise NotImplementedError.new("#{self.class.name}#file_found is not "\
65
+ "implemented. Did you forget to implement this in your subclass or "\
66
+ "module?")
67
+ end # def file_found
68
+
69
+ # This method is called when a file is deleted.
70
+ #
71
+ # * path - the string path of the file deleted
72
+ #
73
+ # You must implement this in your subclass or module for it
74
+ # to work with EventMachine::watch_glob
75
+ public
76
+ def file_deleted(path)
77
+ raise NotImplementedError.new("#{self.class.name}#file_found is not "\
78
+ "implemented. Did you forget to implement this in your subclass or "\
79
+ "module?")
80
+ end # def file_found
81
+
42
82
  private
43
83
  def find_files
44
- @logger.info("Searching for files in #{@pathglob}")
45
- list = Set.new(Dir.glob(@pathglob))
84
+ @logger.info("Searching for files in #{@glob}")
85
+ list = Set.new(Dir.glob(@glob))
46
86
  list.each do |path|
47
87
  next if @files.include?(path)
48
88
  add(path)
@@ -53,56 +93,82 @@ class EventMachine::FileGlobWatch
53
93
  end
54
94
  end # def find_files
55
95
 
56
- public
96
+ # Remove a file from being watched and notify file_deleted()
97
+ private
57
98
  def remove(path)
58
99
  @files.delete(path)
59
100
  @watches.delete(path)
60
- file_removed(path)
61
- end
101
+ file_deleted(path)
102
+ end # def remove
62
103
 
104
+ # Add a file to watch and notify file_found()
63
105
  private
64
106
  def add(path)
65
107
  @files.add(path)
66
108
 
67
109
  # If EventMachine::watch_file fails, that's ok, I guess.
68
110
  # We'll still find the file 'missing' from the next glob attempt.
69
- begin
111
+ #begin
70
112
  # EM currently has a bug that only the first handler for a watch_file
71
113
  # on each file gets events. This causes globtails to never get data
72
114
  # since the glob is watching the file already.
73
115
  # Until we fix that, let's skip file watching here.
74
- #@watches[path] = EventMachine::watch_file(path, GlobFileWatch, self)
75
- rescue Errno::EACCES => e
76
- @logger.warn(e)
77
- end
116
+ #@watches[path] = EventMachine::watch_file(path, FileWatcher, self) do |path|
117
+ # remove(path)
118
+ #end
119
+ #rescue Errno::EACCES => e
120
+ #@logger.warn(e)
121
+ #end
78
122
  file_found(path)
79
123
  end # def watch
80
124
 
81
125
  private
82
- class GlobFileWatch < EventMachine::FileWatch
83
- def initialize(globwatch)
126
+ class FileWatcher < EventMachine::FileWatch
127
+ def initialize(globwatch, &block)
84
128
  @globwatch = globwatch
129
+ @block = block
85
130
  end
86
131
 
87
132
  def file_moved
88
133
  stop_watching
89
- @globwatch.remove(path)
134
+ block.call path
90
135
  end
91
136
 
92
137
  def file_deleted
93
- @globwatch.remove(path)
138
+ block.call path
94
139
  end
95
- end # class GlobFileWatch < EventMachine::FileWatch
140
+ end # class EventMachine::FileGlobWatch::FileWatcher < EventMachine::FileWatch
96
141
  end # class EventMachine::FileGlobWatch
97
142
 
143
+ # A glob tailer for EventMachine
144
+ #
145
+ # This class combines features of EventMachine::file_tail and
146
+ # EventMachine::watch_glob.
147
+ #
148
+ # You won't generally subclass this class (See EventMachine::FileGlobWatch)
149
+ #
150
+ # See also: EventMachine::glob_tail
151
+ #
98
152
  class EventMachine::FileGlobWatchTail < EventMachine::FileGlobWatch
153
+ # Initialize a new file glob tail.
154
+ #
155
+ # path should be a glob or file path.
156
+ # handler should be a module or subclass of EventMachine::FileTail
157
+ # See also EventMachine::file_tail
158
+ # interval is how often (seconds) the glob path should be scanned
159
+ # exclude is an array of Regexp (or anything with .match) for
160
+ # excluding from things to tail
161
+ # The remainder of arguments are passed to EventMachine::file_tail as
162
+ # EventMachine::file_tail(path_found, handler, *args)
163
+ public
99
164
  def initialize(path, handler=nil, interval=60, exclude=[], *args)
100
165
  super(path, interval)
101
166
  @handler = handler
102
167
  @args = args
103
168
  @exclude = exclude
104
- end
169
+ end # def initialize
105
170
 
171
+ public
106
172
  def file_found(path)
107
173
  begin
108
174
  @logger.info "#{self.class}: Trying #{path}"
@@ -120,25 +186,32 @@ class EventMachine::FileGlobWatchTail < EventMachine::FileGlobWatch
120
186
  file_error(path, e)
121
187
  rescue Errno::EISDIR => e
122
188
  file_error(path, e)
123
- end
124
- end
189
+ end
190
+ end # def file_found
125
191
 
192
+ public
126
193
  def file_excluded(path)
127
194
  @logger.info "#{self.class}: Skipping path #{path} due to exclude rule"
128
- end
195
+ end # def file_excluded
129
196
 
130
- def file_removed(path)
197
+ public
198
+ def file_deleted(path)
131
199
  # Nothing to do
132
- end
200
+ end # def file_deleted
133
201
 
202
+ public
134
203
  def file_error(path, e)
135
204
  $stderr.puts "#{e.class} while trying to tail #{path}"
136
205
  # otherwise, drop the error by default
137
- end
206
+ end # def file_error
138
207
  end # class EventMachine::FileGlobWatchHandler
139
208
 
140
- # Add EventMachine::glob_tail
141
209
  module EventMachine
210
+ # Watch a glob and tail any files found.
211
+ #
212
+ # 'glob' should be a string path or glob, such as /var/log/*.log
213
+ # handler must be a module or subclass of EventMachine::FileGlobWatchTail
214
+ # See EventMachine::FileGlobWatchTail for the callback methods.
142
215
  def self.glob_tail(glob, handler=nil, *args)
143
216
  handler = EventMachine::FileGlobWatch if handler == nil
144
217
  args.unshift(glob)
@@ -146,12 +219,25 @@ module EventMachine
146
219
  c = klass.new(*args)
147
220
  yield c if block_given?
148
221
  return c
149
- end
150
-
151
- def self.watch_glob(path, handler=nil, *args)
222
+ end
223
+
224
+ # Watch a glob for any files.
225
+ #
226
+ # * glob - a string path or glob, such as "/var/log/*.log"
227
+ # * handler - must be a module or a subclass of EventMachine::FileGlobWatch
228
+ #
229
+ # The remaining (optional) arguments are passed to your handler like this:
230
+ # If you call this:
231
+ # EventMachine.watch_glob("/var/log/*.log", YourHandler, 1, 2, 3, ...)
232
+ # This will be invoked when new matching files are found:
233
+ # YourHandler.new(path_found, 1, 2, 3, ...)
234
+ # ^ path_found is the new path found by the glob
235
+ #
236
+ # See EventMachine::FileGlobWatch for the callback methods.
237
+ def self.watch_glob(glob, handler=nil, *args)
152
238
  # This code mostly styled on what EventMachine does in many of it's other
153
239
  # methods.
154
- args = [path, *args]
240
+ args = [glob, *args]
155
241
  klass = klass_from_handler(EventMachine::FileGlobWatch, handler, *args);
156
242
  c = klass.new(*args)
157
243
  yield c if block_given?
data/samples/globwatch.rb CHANGED
@@ -9,7 +9,7 @@ class Watcher < EventMachine::FileGlobWatch
9
9
  super(pathglob, interval)
10
10
  end
11
11
 
12
- def file_removed(path)
12
+ def file_deleted(path)
13
13
  puts "Removed: #{path}"
14
14
  end
15
15
 
data/test/alltests.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'test_filetail'
2
+ require 'test_glob'
data/test/test_glob.rb CHANGED
@@ -25,7 +25,7 @@ class Watcher < EventMachine::FileGlobWatch
25
25
  @testobj.finish if @data.length == 0
26
26
  end
27
27
 
28
- def file_removed(path)
28
+ def file_deleted(path)
29
29
  @testobj.assert(@data.include?(path), "Expected #{path} in \n#{@data.join("\n")}")
30
30
  @data.delete(path)
31
31
  @testobj.finish if @data.length == 0
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 2
8
- - 20100516235116
9
- version: 0.2.20100516235116
8
+ - 20100517011408
9
+ version: 0.2.20100517011408
10
10
  platform: ruby
11
11
  authors:
12
12
  - Jordan Sissel
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-05-16 00:00:00 -07:00
17
+ date: 2010-05-17 00:00:00 -07:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -46,6 +46,7 @@ files:
46
46
  - samples/globwatch.rb
47
47
  - test/test_filetail.rb
48
48
  - test/test_glob.rb
49
+ - test/alltests.rb
49
50
  - test/testcase_helpers.rb
50
51
  - bin/rtail
51
52
  has_rdoc: true