eventmachine-tail 0.2.20100516235116 → 0.2.20100517011408

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