Module: DuckTest::Platforms::Listener

Extended by:
LoggerHelper
Included in:
Generic::Listener, DuckTest::Platforms::Linux::Listener, Mac::Listener, Windows::Listener
Defined in:
lib/duck_test/platforms/listener.rb

Overview

Data and methods for implementing a listener. Take a look at Generic::Listener for an example of how to implement a custom listener.

Basically, FrameWork::FileManager will…

  • instantiate a listener object.

  • assign a listener_event block to call when a directory / file changes

  • call listener.watch(file_spec) for each directory / file that should be watched.

  • call the start method on the listener to begin the listening thread.

At this point, it is the listeners responsibility to notify the file manager of changed files. Generic::Listener uses #refresh to interrogate the file system for deleted, changed, and new files and will return a list of changed and new files and call the event listener block for each.

Instance Attribute Summary (collapse)

Instance Method Summary (collapse)

Methods included from LoggerHelper

ducklog

Instance Attribute Details

- (Object) block

Returns the value of attribute block



21
22
23
# File 'lib/duck_test/platforms/listener.rb', line 21

def block
  @block
end

Instance Method Details

- (Object) call_listener_event(event)

Executes the block assigned via #listener_event

Parameters:

Returns:

  • (Object)

    The value returned by the block.



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/duck_test/platforms/listener.rb', line 69

def call_listener_event(event)
  value = nil

  begin

    unless self.block.blank?
      value = self.block.call event
    end

  rescue Exception => e
    ducklog.exception e
  end

  return value
end

- (Boolean) changed?(file_spec)

Determines if a directory / file has changed.

file_spec = "/home/my_home/test.com/test/unit/bike_spec.rb"
watched?(file_spec)   # => false  should not be considered changed unless watched

file_spec = "/home/my_home/test.com/test/unit/bike_spec.rb"
watch(file_spec)
watched?(file_spec)   # => false  not changed yet

file_spec = "/home/my_home/test.com/test/unit/bike_spec.rb"
watch(file_spec)
watched?(file_spec)   # => false  not changed yet
FileUtils.touch(file_spec)
watched?(file_spec)   # => true

Returns:

  • (Boolean)

    Returns true if file_spec has changed.



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/duck_test/platforms/listener.rb', line 177

def changed?(file_spec)
  value = false

  # must have a valid file object.
  file_object = self.file_list[file_spec]
  if file_object

    # no SHA for directories
    if file_object[:is_dir]
      value = File.mtime(file_spec).to_f > file_object[:mtime]
    else
      value = File.mtime(file_spec).to_f > file_object[:mtime] || !Digest::SHA1.file(file_spec).to_s.eql?(file_object[:sha])
    end

  end
  return value
end

- (Array) changed_files(file_spec)

Returns changed files within a single directory. This method is intended to be used by listeners that process a single directory as opposed to single files.

what does this thing do?

  • get a list of all the directories / files within the requested directory specified by file_spec

  • loop thru each directory / file in the list

    • each directory / file is added to the watch list. this should cover directories / files that are added after the listener has been started.

    • directories are not added to the return list.

    • if the file being compared is not currently watched or if #changed? says it has been changed, then, the file spec is added to the return list.

Parameters:

  • file_spec (String)

    A file spec that adheres to File.basename, however, the actual file name should be excluded. ONLY the path should be passed.

Returns:

  • (Array)

    Returns changed files



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/duck_test/platforms/listener.rb', line 280

def changed_files(file_spec)
  list = []

  file_list = Dir.glob(File.join(file_spec, "*"))
  file_list.each do |file_spec|

    currently_watched = self.watched?(file_spec)
    # ensure the directory / file is being watched
    # a new directory / file could have been added by the user
    self.watch_file_spec(file_spec)

    unless File.directory?(file_spec)
      if !currently_watched || self.changed?(file_spec)
        list.push(file_spec)
      end
    end
  end

  return list
end

- (Array) dir_list

Maintains an array of file spec that are directories ONLY. Directories are tracked via #file_list, however, this addiontal list is used for a couple of reasons.

  1. It is an Array instead of a Hash making the logic to interrogate the directory structure for changed files a little faster and simpler.

  2. Conveinent for listeners using a code base native to the operating system that tracks changes based on directories instead of individual files. rb-fsevent and rb-fchange are examples. Listeners of this type can simply call #changed_files to get a list of changed files for the directory in question.

Returns:

  • (Array)

    The current list of watched directories.



31
32
33
34
# File 'lib/duck_test/platforms/listener.rb', line 31

def dir_list
  @dir_list ||= []
  return @dir_list
end

- (Hash) file_list

Maintains a Hash of full file specs representing all directories / files being watched. File spec is a full path and file name that adheres to File.basename and is used as a key to find all of the attributes associated with file spec.

file_spec = "/home/my_home/test.com/test/unit"
self.file_list[file_spec]    # => {:mtime=>1327192086.4123762, :is_dir=>true}

file_spec = "/home/my_home/test.com/test/unit/book_spec.rb"
self.file_list[file_spec]    # => {:mtime=>1327283977.2120826, :sha=>"da39a3ee5e6b4b0d3255bfef95601890afd80709"}

FileUtils.touch "/home/my_home/test.com/test/unit/book_spec.rb"
self.file_list[file_spec]    # => {:mtime=>1327283977.2120826, :sha=>"da39a3ee5e6b4b0d3255bfef95601890afd80709", :changed => true}

Returns:

  • (Hash)

    The full Hash of file specs being watched.



51
52
53
54
# File 'lib/duck_test/platforms/listener.rb', line 51

def file_list
  @file_list ||= {}
  return @file_list
end

- (Proc) listener_event(&block)

Sets the block that will be executed by the listener when a file event occurs.

Parameters:

  • block (Proc)
    • The block to execute.

Returns:

  • (Proc)


60
61
62
63
# File 'lib/duck_test/platforms/listener.rb', line 60

def listener_event(&block)
  self.block = block if block_given?
  return self.block
end

- (Array) refresh

Traverses all watched directories and builds a list of all changed files. It does not include directories in the returned list. refresh loops thru all of the items in #dir_list and calls #changed_files for each of them.

Returns:

  • (Array)

    Returns changed files



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/duck_test/platforms/listener.rb', line 243

def refresh
  list = []
  index = 0

  # remove directories / files from the internal lists that no longer exist on disk.
  self.file_list.each do |file_object|
    unless File.exist?(file_object.first)
      if file_object.last[:is_dir]
        self.dir_list.delete(file_object.first)
      end
      self.file_list.delete(file_object.first)
    end
  end

  while index < self.dir_list.length
    list.concat(self.changed_files(self.dir_list[index]))
    index += 1

  end

  return list
end

- (Number) speed

The speed value is intended for use within the thread loop that listens for changes to directories / files.

Returns:

  • (Number)

    The current value of speed.



111
112
113
114
# File 'lib/duck_test/platforms/listener.rb', line 111

def speed
  @speed = 1 unless defined?(@speed)
  return @speed
end

- (Object) speed=(value)

Sets the speed attribute.



117
118
119
# File 'lib/duck_test/platforms/listener.rb', line 117

def speed=(value)
  @speed = value
end

- (NilClass) start

This method is intended to be overridden by the implementing class.

Returns:

  • (NilClass)


88
89
90
91
92
# File 'lib/duck_test/platforms/listener.rb', line 88

def start
  Kernel.trap("INT") do
    self.stop = true
  end
end

- (Boolean) stop

The stop attribute is intended to be used by listeners to control a thread loop while listening for changes to directories / files.

Returns:

  • (Boolean)

    True if the listener should stop listening, otherwise, false.



98
99
100
101
# File 'lib/duck_test/platforms/listener.rb', line 98

def stop
  @stop = false unless defined?(@stop)
  return @stop
end

- (Object) stop=(value)

Sets the stop attribute.



104
105
106
# File 'lib/duck_test/platforms/listener.rb', line 104

def stop=(value)
  @stop = value
end

- (NilClass) update_all

Updates all of tracked attributes for all directories / files being watched. Since this method calls #update_file_spec, all of the existing attributes will be replaced, therefore, addiontal attributes such as :changed true/false will be wiped out.

Returns:

  • (NilClass)


232
233
234
235
236
237
# File 'lib/duck_test/platforms/listener.rb', line 232

def update_all
  self.file_list.each do |item|
    update_file_spec(item.first)
  end
  return nil
end

- (NilClass) update_file_spec(file_spec)

Updates all of the tracked attributes of a watched directory / file. This method is called by methods such as #watch and #watch_file_spec to obtain attributes about a directory / file and store them for later use.

file_spec = "/home/my_home/test.com/test/unit"
update_file_spec(file_spec)
puts file_list(file_spec)    # => {:mtime=>1327290092.5563293, :is_dir=>true}

file_spec = "/home/my_home/test.com/test/unit/book_spec.rb"
update_file_spec(file_spec)
puts file_list(file_spec)    # => {:mtime=>1327290031.8203268, :sha=>"da39a3ee5e6b4b0d3255bfef95601890afd80709"}

Parameters:

  • file_spec (String)

    A file name that adheres to File.basename.

Returns:

  • (NilClass)


209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/duck_test/platforms/listener.rb', line 209

def update_file_spec(file_spec)
  buffer = {}

  buffer[:mtime] = File.mtime(file_spec).to_f

  if File.directory?(file_spec)
    buffer[:is_dir] = true
    self.dir_list.push(file_spec) unless self.dir_list.include?(file_spec)
  else
    # assume it is a file if not a directory
    buffer[:sha] = Digest::SHA1.file(file_spec).to_s
  end

  self.file_list[file_spec] = buffer

  return nil
end

- (NilClass) watch(file_spec)

Note:

Be sure to call #watch_file_spec if you override this method.

Instructs the listener to watch a file spec via a call to #watch_file_spec

Parameters:

Returns:

  • (NilClass)


126
127
128
# File 'lib/duck_test/platforms/listener.rb', line 126

def watch(file_spec)
  watch_file_spec(file_spec)
end

- (NilClass) watch_file_spec(file_spec)

Instructs the listener to watch a file spec. File should be a full path and can be a directory or file. The file spec is tracked internally and methods of this module can be used to interrogate the status and attributes of a file. mtime, sha, changed, etc.

Parameters:

  • file_spec (String)

    A file name that adheres to File.basename.

Returns:

  • (NilClass)


135
136
137
138
# File 'lib/duck_test/platforms/listener.rb', line 135

def watch_file_spec(file_spec)
  update_file_spec(file_spec) unless self.watched?(file_spec)
  return nil
end

- (Boolean) watched?(file_spec)

Determines if a file_spec is being watched. File spec can be a directory or file and should included full path.

file_spec = "/home/my_home/test.com/test/unit"
watch(file_spec)
puts watched?(file_spec)    # => true

file_spec = "/home/my_home/test.com/test/unit/book_spec.rb"
watch(file_spec)
puts watched?(file_spec)    # => true

file_spec = "/home/my_home/test.com/test/unit/bike_spec.rb"
puts watched?(file_spec)    # => false

Parameters:

  • file_spec (String)

    A file name that adheres to File.basename.

Returns:

  • (Boolean)

    Returns true if file_spec is being watched.



156
157
158
# File 'lib/duck_test/platforms/listener.rb', line 156

def watched?(file_spec)
  return self.file_list[file_spec] ? true : false
end