Class: FileWatcher

Inherits:
Object
  • Object
show all
Defined in:
lib/filewatcher.rb

Overview

Simple file watcher. Detect changes in files and directories.

Issues: Currently doesn’t monitor changes in directorynames

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(unexpanded_filenames, *args) ⇒ FileWatcher

Returns a new instance of FileWatcher.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/filewatcher.rb', line 18

def initialize(unexpanded_filenames, *args)
  if(args.first)
    options = args.first
  else
    options = {}
  end
  @unexpanded_filenames = unexpanded_filenames
  @unexpanded_excluded_filenames = options[:exclude]
  @filenames = nil
  @stored_update = nil
  @keep_watching = false
  @pausing = false
  @last_snapshot = mtime_snapshot
  @end_snapshot = nil
  @dontwait = options[:dontwait]
  @show_spinner = options[:spinner]
  @interval = options[:interval]
end

Instance Attribute Details

#filenamesObject

Returns the value of attribute filenames.



6
7
8
# File 'lib/filewatcher.rb', line 6

def filenames
  @filenames
end

Class Method Details

.VERSIONObject



8
9
10
# File 'lib/filewatcher.rb', line 8

def self.VERSION
  return '0.5.3'
end

Instance Method Details

#expand_directories(patterns) ⇒ Object



163
164
165
166
167
168
# File 'lib/filewatcher.rb', line 163

def expand_directories(patterns)
  if(!patterns.kind_of?Array)
    patterns = [patterns]
  end
  patterns.map { |it| Dir[fulldepth(it)] }.flatten.uniq
end

#filesystem_updated?(snapshot_to_use = nil) ⇒ Boolean

Returns:

  • (Boolean)


130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/filewatcher.rb', line 130

def filesystem_updated?(snapshot_to_use = nil)
  snapshot = snapshot_to_use ? snapshot_to_use : mtime_snapshot
  forward_changes = snapshot.to_a - @last_snapshot.to_a

  forward_changes.each do |file,mtime|
    @updated_file = file
    unless @last_snapshot.fetch(@updated_file,false)
      @last_snapshot[file] = mtime
      @event = :new
      return true
    else
      @last_snapshot[file] = mtime
      @event = :changed
      return true
    end
  end

  backward_changes = @last_snapshot.to_a - snapshot.to_a
  forward_names = forward_changes.map{|change| change.first}
  backward_changes.reject!{|f,m| forward_names.include?(f)}
  backward_changes.each do |file,mtime|
    @updated_file = file
    @last_snapshot.delete(file)
    @event = :delete
    return true
  end
  return false
end

#finalize(&on_update) ⇒ Object

Calls the update block repeatedly until all changes in the current snapshot are dealt with



94
95
96
97
98
99
100
101
102
103
# File 'lib/filewatcher.rb', line 94

def finalize(&on_update)
  on_update = @stored_update if !block_given?
  snapshot = @end_snapshot ? @end_snapshot : mtime_snapshot
  while filesystem_updated?(snapshot)
    update_spinner('Finalizing')
    on_update.call(@updated_file, @event)
  end
  @end_snapshot =nil
  return nil
end

#last_found_filenamesObject



159
160
161
# File 'lib/filewatcher.rb', line 159

def last_found_filenames
  @last_snapshot.keys
end

#mtime_snapshotObject

Takes a snapshot of the current status of watched files. (Allows avoidance of potential race condition during #finalize)



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/filewatcher.rb', line 107

def mtime_snapshot
  snapshot = {}
  @filenames = expand_directories(@unexpanded_filenames)

  if(@unexpanded_excluded_filenames != nil and @unexpanded_excluded_filenames.size > 0)
    # Remove files in the exclude filenames list
    @filtered_filenames = []
    @excluded_filenames = expand_directories(@unexpanded_excluded_filenames)
    @filenames.each do |filename|
      if(not(@excluded_filenames.include?(filename)))
        @filtered_filenames << filename
      end
    end
    @filenames = @filtered_filenames
  end
  
  @filenames.each do |filename|
    mtime = File.exist?(filename) ? File.stat(filename).mtime : Time.new(0)
    snapshot[filename] = mtime
  end
  return snapshot
end

#pauseObject



67
68
69
70
71
72
# File 'lib/filewatcher.rb', line 67

def pause
  @pausing = true
  update_spinner('Initiating pause')
  Kernel.sleep @sleep # Ensure we wait long enough to enter pause loop
                      # in #watch
end

#resumeObject



74
75
76
77
78
79
80
81
82
# File 'lib/filewatcher.rb', line 74

def resume
  if !@keep_watching || !@pausing
    raise "Can't resume unless #watch and #pause were first called"
  end
  @last_snapshot = mtime_snapshot  # resume with fresh snapshot
  @pausing = false
  update_spinner('Resuming')
  Kernel.sleep @sleep # Wait long enough to exit pause loop in #watch
end

#stopObject

Ends the watch, allowing any remaining changes to be finalized. Used mainly in multi-threaded situations.



86
87
88
89
90
# File 'lib/filewatcher.rb', line 86

def stop
  @keep_watching = false
  update_spinner('Stopping')
  return nil
end

#update_spinner(label) ⇒ Object



12
13
14
15
16
# File 'lib/filewatcher.rb', line 12

def update_spinner(label)
  return nil unless @show_spinner
  @spinner ||= %w(\\ | / -)
  print "#{' ' * 30}\r#{label}  #{@spinner.rotate!.first}\r"
end

#watch(sleep = 0.5, &on_update) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/filewatcher.rb', line 37

def watch(sleep=0.5, &on_update)
  trap("SIGINT") {return }
  @sleep = sleep
  if(@interval and @interval > 0)
    @sleep = @interval
  end    
  @stored_update = on_update
  @keep_watching = true
  if(@dontwait)
    yield '',''
  end
  while @keep_watching
    @end_snapshot = mtime_snapshot if @pausing
    while @keep_watching && @pausing
      update_spinner('Pausing')
      Kernel.sleep @sleep
    end
    while @keep_watching && !filesystem_updated? && !@pausing
      update_spinner('Watching')
      Kernel.sleep @sleep
    end
    # test and null @updated_file to prevent yielding the last
    # file twice if @keep_watching has just been set to false
    yield @updated_file, @event if @updated_file
    @updated_file = nil
  end
  @end_snapshot = mtime_snapshot
  finalize(&on_update)
end