File: eventable_scanner.rb

package info (click to toggle)
ruby-directory-watcher 1.5.1-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 224 kB
  • ctags: 224
  • sloc: ruby: 1,411; makefile: 5
file content (242 lines) | stat: -rw-r--r-- 6,522 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# An Eventable Scanner is one that can be utilized by something that has an
# Event Loop. It is intended to be subclassed by classes that implement the
# specific event loop semantics for say EventMachine or Cool.io.
#
# The Events that the EventableScanner is programmed for are:
#
# on_scan     - this should be called every +interval+ times
# on_modified - If the event loop can monitor individual files then this should
#               be called when the file is modified
# on_removed  - Similar to on_modified but called when a file is removed.
#
# Sub classes are required to implement the following:
#
#   start_loop_with_attached_scan_timer() - Instance Method
#     This method is to start up the loop, if necessary assign to @loop_thread
#     instance variable the Thread that is controlling the event loop.
#
#     This method must also assign an object to @timer which is what does the
#     periodic scanning of the globs. This object must respond to +detach()+ so
#     that it may be detached from the event loop.
#
#   stop_loop() - Instance Method
#     This method must shut down the event loop, or detach these classes from
#     the event loop if we just attached to an existing event loop.
#
#   Watcher - An Embedded class
#     This is a class that must have a class method +watcher(path,scanner)+
#     which is used to instantiate a file watcher. The Watcher instance must
#     respond to +detach()+ so that it may be independently detached from the
#     event loop.
#
class DirectoryWatcher::EventableScanner
  include DirectoryWatcher::Logable

  # A Hash of Watcher objects.
  attr_reader :watchers

  # call-seq:
  #    EventableScanner.new( config )
  #
  # config - the Configuration instances
  #
  def initialize( config )
    @config = config
    @scan_and_queue = DirectoryWatcher::ScanAndQueue.new(config.glob, config.collection_queue)
    @watchers = {}
    @stopping = false
    @timer = nil
    @loop_thread = nil
    @paused = false
  end

  # The queue on which to put FileStat and Scan items.
  #
  def collection_queue
    @config.collection_queue
  end

  # The interval at which to scan
  #
  def interval
    @config.interval
  end

  # Returns +true+ if the scanner is currently running. Returns +false+ if
  # this is not the case.
  #
  def running?
    return !@stopping if @timer
    return false
  end

  # Start up the scanner. If the scanner is already running, nothing happens.
  #
  def start
    return if running?
    logger.debug "starting scanner"
    start_loop_with_attached_scan_timer
  end

  # Stop the scanner. If the scanner is not running, nothing happens.
  #
  def stop
    return unless running?
    logger.debug "stoping scanner"
    @stopping = true
    teardown_timer_and_watches
    @stopping = false
    stop_loop
  end

  # Pause the scanner.
  #
  # Pausing the scanner does not stop the scanning per se, it stops items from
  # being sent to the collection queue
  #
  def pause
    logger.debug "pausing scanner"
    @paused = true
  end

  # Resume the scanner.
  #
  # This removes the blockage on sending items to the collection queue.
  #
  def resume
    logger.debug "resuming scanner"
    @paused = false
  end

  # Is the Scanner currently paused.
  #
  def paused?
    @paused
  end

  # EventableScanners do not join
  #
  def join( limit = nil )
  end

  # Do a single scan and send those items to the collection queue.
  #
  def run
    logger.debug "running scan and queue"
    @scan_and_queue.scan_and_queue
  end

  # Setting maximum iterations means hooking into the periodic timer event and
  # counting the number of times it is going on. This also resets the current
  # iterations count
  #
  def maximum_iterations=(value)
    unless value.nil?
      value = Integer(value)
      raise ArgumentError, "maximum iterations must be >= 1" unless value >= 1
    end
    @iterations = 0
    @maximum_iterations = value
  end
  attr_reader :maximum_iterations
  attr_reader :iterations

  # Have we completed up to the maximum_iterations?
  #
  def finished_iterations?
    self.iterations >= self.maximum_iterations
  end

  # This callback is invoked by the Timer instance when it is triggered by
  # the Loop. This method will check for added files and stable files
  # and notify the directory watcher accordingly.
  #
  def on_scan
    logger.debug "on_scan called"
    scan_and_watch_files
    progress_towards_maximum_iterations
  end

  # This callback is invoked by the Watcher instance when it is triggered by the
  # loop for file modifications.
  #
  def on_modified(watcher, new_stat)
    logger.debug "on_modified called"
    queue_item(new_stat)
  end

  # This callback is invoked by the Watcher instance when it is triggered by the
  # loop for file removals
  #
  def on_removed(watcher, new_stat)
    logger.debug "on_removed called"
    unwatch_file(watcher.path)
    queue_item(new_stat)
  end

  #######
  private
  #######

  # Send the given item to the collection queue
  #
  def queue_item( item )
    if paused? then
      logger.debug "Not queueing item, we're paused"
    else
      logger.debug "enqueuing #{item} to #{collection_queue}"
      collection_queue.enq item
    end
  end


  # Run a single scan and turn on watches for all the files found in that scan
  # that do not already have watchers on them.
  #
  def scan_and_watch_files
    logger.debug "scanning and watching files"
    scan = @scan_and_queue.scan_and_queue
    scan.results.each do |stat|
      watch_file(stat.path)
    end
  end

  # Remove the timer and the watches from the event loop
  #
  def teardown_timer_and_watches
    @timer.detach rescue nil
    @timer = nil

    @watchers.each_value {|w| w.detach}
    @watchers.clear
  end

  # Create and return a new Watcher instance for the given filename _fn_.
  # A watcher will only be created once for a particular fn.
  #
  def watch_file( fn )
    unless @watchers[fn] then
      logger.debug "Watching file #{fn}"
      w = self.class::Watcher.watch(fn, self)
      @watchers[fn] = w
    end
  end

  # Remove the watcher instance from our tracking
  #
  def unwatch_file( fn )
    logger.debug "Unwatching file #{fn}"
    @watchers.delete(fn)
  end

  # Make progress towards maximum iterations. And if we get there, then stop
  # monitoring files.
  #
  def progress_towards_maximum_iterations
    if maximum_iterations then
      @iterations += 1
      stop if finished_iterations?
    end
  end
  # :startdoc:
end  # class DirectoryWatcher::Eventablecanner