listen 3.0.8 → 3.7.1
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.
- checksums.yaml +5 -5
- data/CONTRIBUTING.md +10 -3
- data/README.md +248 -79
- data/bin/listen +3 -4
- data/lib/listen/adapter/base.rb +26 -34
- data/lib/listen/adapter/bsd.rb +9 -8
- data/lib/listen/adapter/config.rb +4 -5
- data/lib/listen/adapter/darwin.rb +35 -46
- data/lib/listen/adapter/linux.rb +17 -17
- data/lib/listen/adapter/polling.rb +9 -6
- data/lib/listen/adapter/windows.rb +19 -22
- data/lib/listen/adapter.rb +25 -25
- data/lib/listen/backend.rb +7 -10
- data/lib/listen/change.rb +18 -27
- data/lib/listen/cli.rb +6 -6
- data/lib/listen/directory.rb +15 -9
- data/lib/listen/error.rb +11 -0
- data/lib/listen/event/config.rb +9 -28
- data/lib/listen/event/loop.rb +44 -67
- data/lib/listen/event/processor.rb +41 -37
- data/lib/listen/event/queue.rb +14 -18
- data/lib/listen/file.rb +23 -8
- data/lib/listen/fsm.rb +74 -72
- data/lib/listen/listener/config.rb +5 -9
- data/lib/listen/listener.rb +26 -22
- data/lib/listen/logger.rb +24 -20
- data/lib/listen/monotonic_time.rb +27 -0
- data/lib/listen/options.rb +11 -8
- data/lib/listen/queue_optimizer.rb +15 -18
- data/lib/listen/record/entry.rb +8 -4
- data/lib/listen/record/symlink_detector.rb +9 -7
- data/lib/listen/record.rb +40 -37
- data/lib/listen/silencer/controller.rb +2 -0
- data/lib/listen/silencer.rb +30 -21
- data/lib/listen/thread.rb +54 -0
- data/lib/listen/version.rb +3 -1
- data/lib/listen.rb +14 -22
- metadata +20 -28
- data/lib/listen/internals/thread_pool.rb +0 -29
    
        data/lib/listen/adapter/base.rb
    CHANGED
    
    | @@ -1,16 +1,17 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'listen/options'
         | 
| 2 4 | 
             
            require 'listen/record'
         | 
| 3 5 | 
             
            require 'listen/change'
         | 
| 6 | 
            +
            require 'listen/thread'
         | 
| 4 7 |  | 
| 5 8 | 
             
            module Listen
         | 
| 6 9 | 
             
              module Adapter
         | 
| 7 10 | 
             
                class Base
         | 
| 8 | 
            -
                  attr_reader :options
         | 
| 11 | 
            +
                  attr_reader :options, :config
         | 
| 9 12 |  | 
| 10 13 | 
             
                  # TODO: only used by tests
         | 
| 11 | 
            -
                  DEFAULTS = {}
         | 
| 12 | 
            -
             | 
| 13 | 
            -
                  attr_reader :config
         | 
| 14 | 
            +
                  DEFAULTS = {}.freeze
         | 
| 14 15 |  | 
| 15 16 | 
             
                  def initialize(config)
         | 
| 16 17 | 
             
                    @started = false
         | 
| @@ -28,9 +29,10 @@ module Listen | |
| 28 29 | 
             
                  end
         | 
| 29 30 |  | 
| 30 31 | 
             
                  # TODO: it's a separate method as a temporary workaround for tests
         | 
| 32 | 
            +
                  # rubocop:disable Metrics/MethodLength
         | 
| 31 33 | 
             
                  def configure
         | 
| 32 34 | 
             
                    if @configured
         | 
| 33 | 
            -
                       | 
| 35 | 
            +
                      Listen.logger.warn('Adapter already configured!')
         | 
| 34 36 | 
             
                      return
         | 
| 35 37 | 
             
                    end
         | 
| 36 38 |  | 
| @@ -49,11 +51,12 @@ module Listen | |
| 49 51 | 
             
                    # TODO: separate config per directory (some day maybe)
         | 
| 50 52 | 
             
                    change_config = Change::Config.new(config.queue, config.silencer)
         | 
| 51 53 | 
             
                    config.directories.each do |dir|
         | 
| 52 | 
            -
                      record = Record.new(dir)
         | 
| 54 | 
            +
                      record = Record.new(dir, config.silencer)
         | 
| 53 55 | 
             
                      snapshot = Change.new(change_config, record)
         | 
| 54 56 | 
             
                      @snapshots[dir] = snapshot
         | 
| 55 57 | 
             
                    end
         | 
| 56 58 | 
             
                  end
         | 
| 59 | 
            +
                  # rubocop:enable Metrics/MethodLength
         | 
| 57 60 |  | 
| 58 61 | 
             
                  def started?
         | 
| 59 62 | 
             
                    @started
         | 
| @@ -63,48 +66,39 @@ module Listen | |
| 63 66 | 
             
                    configure
         | 
| 64 67 |  | 
| 65 68 | 
             
                    if started?
         | 
| 66 | 
            -
                       | 
| 69 | 
            +
                      Listen.logger.warn('Adapter already started!')
         | 
| 67 70 | 
             
                      return
         | 
| 68 71 | 
             
                    end
         | 
| 69 72 |  | 
| 70 73 | 
             
                    @started = true
         | 
| 71 74 |  | 
| 72 | 
            -
                     | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 75 | 
            -
                        @snapshots.values.each do |snapshot|
         | 
| 76 | 
            -
                          _timed('Record.build()') { snapshot.record.build }
         | 
| 77 | 
            -
                        end
         | 
| 78 | 
            -
                        _run
         | 
| 79 | 
            -
                      rescue
         | 
| 80 | 
            -
                        msg = 'run() in thread failed: %s:\n'\
         | 
| 81 | 
            -
                          ' %s\n\ncalled from:\n %s'
         | 
| 82 | 
            -
                        _log_exception(msg, calling_stack)
         | 
| 83 | 
            -
                        raise # for unit tests mostly
         | 
| 75 | 
            +
                    @run_thread = Listen::Thread.new("run_thread") do
         | 
| 76 | 
            +
                      @snapshots.each_value do |snapshot|
         | 
| 77 | 
            +
                        _timed('Record.build()') { snapshot.record.build }
         | 
| 84 78 | 
             
                      end
         | 
| 79 | 
            +
                      _run
         | 
| 85 80 | 
             
                    end
         | 
| 86 81 | 
             
                  end
         | 
| 87 82 |  | 
| 88 83 | 
             
                  def stop
         | 
| 89 84 | 
             
                    _stop
         | 
| 90 | 
            -
             | 
| 91 | 
            -
             | 
| 92 | 
            -
                  def self.usable?
         | 
| 93 | 
            -
                    const_get('OS_REGEXP') =~ RbConfig::CONFIG['target_os']
         | 
| 85 | 
            +
                    config.queue.close # this causes queue.pop to return `nil` to the front-end
         | 
| 94 86 | 
             
                  end
         | 
| 95 87 |  | 
| 96 88 | 
             
                  private
         | 
| 97 89 |  | 
| 98 90 | 
             
                  def _stop
         | 
| 91 | 
            +
                    @run_thread&.kill
         | 
| 92 | 
            +
                    @run_thread = nil
         | 
| 99 93 | 
             
                  end
         | 
| 100 94 |  | 
| 101 95 | 
             
                  def _timed(title)
         | 
| 102 | 
            -
                    start =  | 
| 96 | 
            +
                    start = MonotonicTime.now
         | 
| 103 97 | 
             
                    yield
         | 
| 104 | 
            -
                    diff =  | 
| 105 | 
            -
                    Listen | 
| 98 | 
            +
                    diff = MonotonicTime.now - start
         | 
| 99 | 
            +
                    Listen.logger.info format('%s: %.05f seconds', title, diff)
         | 
| 106 100 | 
             
                  rescue
         | 
| 107 | 
            -
                    Listen | 
| 101 | 
            +
                    Listen.logger.warn "#{title} crashed: #{$ERROR_INFO.inspect}"
         | 
| 108 102 | 
             
                    raise
         | 
| 109 103 | 
             
                  end
         | 
| 110 104 |  | 
| @@ -114,10 +108,6 @@ module Listen | |
| 114 108 | 
             
                    @snapshots[dir].invalidate(type, rel_path, options)
         | 
| 115 109 | 
             
                  end
         | 
| 116 110 |  | 
| 117 | 
            -
                  def _log(*args, &block)
         | 
| 118 | 
            -
                    self.class.send(:_log, *args, &block)
         | 
| 119 | 
            -
                  end
         | 
| 120 | 
            -
             | 
| 121 111 | 
             
                  def _log_exception(msg, caller_stack)
         | 
| 122 112 | 
             
                    formatted = format(
         | 
| 123 113 | 
             
                      msg,
         | 
| @@ -126,11 +116,13 @@ module Listen | |
| 126 116 | 
             
                      caller_stack * "\n"
         | 
| 127 117 | 
             
                    )
         | 
| 128 118 |  | 
| 129 | 
            -
                     | 
| 119 | 
            +
                    Listen.logger.error(formatted)
         | 
| 130 120 | 
             
                  end
         | 
| 131 121 |  | 
| 132 | 
            -
                   | 
| 133 | 
            -
                     | 
| 122 | 
            +
                  class << self
         | 
| 123 | 
            +
                    def usable?
         | 
| 124 | 
            +
                      const_get('OS_REGEXP') =~ RbConfig::CONFIG['target_os']
         | 
| 125 | 
            +
                    end
         | 
| 134 126 | 
             
                  end
         | 
| 135 127 | 
             
                end
         | 
| 136 128 | 
             
              end
         | 
    
        data/lib/listen/adapter/bsd.rb
    CHANGED
    
    | @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            # Listener implementation for BSD's `kqueue`.
         | 
| 2 4 | 
             
            # @see http://www.freebsd.org/cgi/man.cgi?query=kqueue
         | 
| 3 5 | 
             
            # @see https://github.com/mat813/rb-kqueue/blob/master/lib/rb-kqueue/queue.rb
         | 
| @@ -5,7 +7,7 @@ | |
| 5 7 | 
             
            module Listen
         | 
| 6 8 | 
             
              module Adapter
         | 
| 7 9 | 
             
                class BSD < Base
         | 
| 8 | 
            -
                  OS_REGEXP = /bsd|dragonfly/i
         | 
| 10 | 
            +
                  OS_REGEXP = /bsd|dragonfly/i.freeze
         | 
| 9 11 |  | 
| 10 12 | 
             
                  DEFAULTS = {
         | 
| 11 13 | 
             
                    events: [
         | 
| @@ -16,7 +18,7 @@ module Listen | |
| 16 18 | 
             
                      :rename
         | 
| 17 19 | 
             
                      # :link, :revoke
         | 
| 18 20 | 
             
                    ]
         | 
| 19 | 
            -
                  }
         | 
| 21 | 
            +
                  }.freeze
         | 
| 20 22 |  | 
| 21 23 | 
             
                  BUNDLER_DECLARE_GEM = <<-EOS.gsub(/^ {6}/, '')
         | 
| 22 24 | 
             
                    Please add the following to your Gemfile to avoid polling for changes:
         | 
| @@ -38,9 +40,9 @@ module Listen | |
| 38 40 |  | 
| 39 41 | 
             
                  private
         | 
| 40 42 |  | 
| 41 | 
            -
                  def _configure(directory, & | 
| 43 | 
            +
                  def _configure(directory, &callback)
         | 
| 42 44 | 
             
                    @worker ||= KQueue::Queue.new
         | 
| 43 | 
            -
                    @callback =  | 
| 45 | 
            +
                    @callback = callback
         | 
| 44 46 | 
             
                    # use Record to make a snapshot of dir, so we
         | 
| 45 47 | 
             
                    # can detect new files
         | 
| 46 48 | 
             
                    _find(directory.to_s) { |path| _watch_file(path, @worker) }
         | 
| @@ -71,8 +73,7 @@ module Listen | |
| 71 73 | 
             
                  def _change(event_flags)
         | 
| 72 74 | 
             
                    { modified: [:attrib, :extend],
         | 
| 73 75 | 
             
                      added:    [:write],
         | 
| 74 | 
            -
                      removed:  [:rename, :delete]
         | 
| 75 | 
            -
                    }.each do |change, flags|
         | 
| 76 | 
            +
                      removed:  [:rename, :delete] }.each do |change, flags|
         | 
| 76 77 | 
             
                      return change unless (flags & event_flags).empty?
         | 
| 77 78 | 
             
                    end
         | 
| 78 79 | 
             
                    nil
         | 
| @@ -85,7 +86,7 @@ module Listen | |
| 85 86 | 
             
                  def _watch_for_new_file(event)
         | 
| 86 87 | 
             
                    queue = event.watcher.queue
         | 
| 87 88 | 
             
                    _find(_event_path(event).to_s) do |file_path|
         | 
| 88 | 
            -
                      unless queue.watchers. | 
| 89 | 
            +
                      unless queue.watchers.find { |_, v| v.path == file_path.to_s }
         | 
| 89 90 | 
             
                        _watch_file(file_path, queue)
         | 
| 90 91 | 
             
                      end
         | 
| 91 92 | 
             
                    end
         | 
| @@ -94,7 +95,7 @@ module Listen | |
| 94 95 | 
             
                  def _watch_file(path, queue)
         | 
| 95 96 | 
             
                    queue.watch_file(path, *options.events, &@callback)
         | 
| 96 97 | 
             
                  rescue Errno::ENOENT => e
         | 
| 97 | 
            -
                     | 
| 98 | 
            +
                    Listen.logger.warn "kqueue: watch file failed: #{e.message}"
         | 
| 98 99 | 
             
                  end
         | 
| 99 100 |  | 
| 100 101 | 
             
                  # Quick rubocop workaround
         | 
| @@ -1,17 +1,16 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'pathname'
         | 
| 2 4 |  | 
| 3 5 | 
             
            module Listen
         | 
| 4 6 | 
             
              module Adapter
         | 
| 5 7 | 
             
                class Config
         | 
| 6 | 
            -
                  attr_reader :directories
         | 
| 7 | 
            -
                  attr_reader :silencer
         | 
| 8 | 
            -
                  attr_reader :queue
         | 
| 9 | 
            -
                  attr_reader :adapter_options
         | 
| 8 | 
            +
                  attr_reader :directories, :silencer, :queue, :adapter_options
         | 
| 10 9 |  | 
| 11 10 | 
             
                  def initialize(directories, queue, silencer, adapter_options)
         | 
| 12 11 | 
             
                    # Default to current directory if no directories are supplied
         | 
| 13 12 | 
             
                    directories = [Dir.pwd] if directories.to_a.empty?
         | 
| 14 | 
            -
             | 
| 13 | 
            +
             | 
| 15 14 | 
             
                    # TODO: fix (flatten, array, compact?)
         | 
| 16 15 | 
             
                    @directories = directories.map do |directory|
         | 
| 17 16 | 
             
                      Pathname.new(directory.to_s).realpath
         | 
| @@ -1,15 +1,16 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'listen/thread'
         | 
| 3 4 |  | 
| 4 5 | 
             
            module Listen
         | 
| 5 6 | 
             
              module Adapter
         | 
| 6 7 | 
             
                # Adapter implementation for Mac OS X `FSEvents`.
         | 
| 7 8 | 
             
                #
         | 
| 8 9 | 
             
                class Darwin < Base
         | 
| 9 | 
            -
                  OS_REGEXP = /darwin(?<major_version>1\d+)/i
         | 
| 10 | 
            +
                  OS_REGEXP = /darwin(?<major_version>(1|2)\d+)/i.freeze
         | 
| 10 11 |  | 
| 11 12 | 
             
                  # The default delay between checking for changes.
         | 
| 12 | 
            -
                  DEFAULTS = { latency: 0.1 }
         | 
| 13 | 
            +
                  DEFAULTS = { latency: 0.1 }.freeze
         | 
| 13 14 |  | 
| 14 15 | 
             
                  INCOMPATIBLE_GEM_VERSION = <<-EOS.gsub(/^ {8}/, '')
         | 
| 15 16 | 
             
                    rb-fsevent > 0.9.4 no longer supports OS X 10.6 through 10.8.
         | 
| @@ -22,66 +23,54 @@ module Listen | |
| 22 23 | 
             
                  EOS
         | 
| 23 24 |  | 
| 24 25 | 
             
                  def self.usable?
         | 
| 26 | 
            +
                    version = RbConfig::CONFIG['target_os'][OS_REGEXP, :major_version]
         | 
| 27 | 
            +
                    return false unless version
         | 
| 28 | 
            +
                    return true if version.to_i >= 13 # darwin13 is OS X 10.9
         | 
| 29 | 
            +
             | 
| 25 30 | 
             
                    require 'rb-fsevent'
         | 
| 26 | 
            -
                     | 
| 27 | 
            -
                    return true if  | 
| 28 | 
            -
                    return true if Gem::Version.new(FSEvent::VERSION) <= Gem::Version.new('0.9.4')
         | 
| 31 | 
            +
                    fsevent_version = Gem::Version.new(FSEvent::VERSION)
         | 
| 32 | 
            +
                    return true if fsevent_version <= Gem::Version.new('0.9.4')
         | 
| 29 33 | 
             
                    Kernel.warn INCOMPATIBLE_GEM_VERSION
         | 
| 30 34 | 
             
                    false
         | 
| 31 35 | 
             
                  end
         | 
| 32 36 |  | 
| 33 37 | 
             
                  private
         | 
| 34 38 |  | 
| 35 | 
            -
                  # NOTE: each directory gets a DIFFERENT callback!
         | 
| 36 39 | 
             
                  def _configure(dir, &callback)
         | 
| 37 | 
            -
                     | 
| 38 | 
            -
             | 
| 39 | 
            -
                    @workers ||= ::Queue.new
         | 
| 40 | 
            -
                    @workers << FSEvent.new.tap do |worker|
         | 
| 41 | 
            -
                      _log :debug, "fsevent: watching: #{dir.to_s.inspect}"
         | 
| 42 | 
            -
                      worker.watch(dir.to_s, opts, &callback)
         | 
| 43 | 
            -
                    end
         | 
| 40 | 
            +
                    @callbacks[dir] = callback
         | 
| 44 41 | 
             
                  end
         | 
| 45 42 |  | 
| 46 43 | 
             
                  def _run
         | 
| 47 | 
            -
                     | 
| 48 | 
            -
             | 
| 49 | 
            -
                     | 
| 50 | 
            -
                     | 
| 51 | 
            -
                     | 
| 52 | 
            -
                     | 
| 44 | 
            +
                    require 'rb-fsevent'
         | 
| 45 | 
            +
                    worker = FSEvent.new
         | 
| 46 | 
            +
                    dirs_to_watch = @callbacks.keys.map(&:to_s)
         | 
| 47 | 
            +
                    Listen.logger.info { "fsevent: watching: #{dirs_to_watch.inspect}" }
         | 
| 48 | 
            +
                    worker.watch(dirs_to_watch, { latency: options.latency }, &method(:_process_changes))
         | 
| 49 | 
            +
                    @worker_thread = Listen::Thread.new("worker_thread") { worker.run }
         | 
| 53 50 | 
             
                  end
         | 
| 54 51 |  | 
| 55 | 
            -
                  def  | 
| 56 | 
            -
                     | 
| 57 | 
            -
             | 
| 58 | 
            -
                      new_path = Pathname.new(path.sub(/\/$/, ''))
         | 
| 59 | 
            -
                      _log :debug, "fsevent: #{new_path}"
         | 
| 60 | 
            -
                      # TODO: does this preserve symlinks?
         | 
| 61 | 
            -
                      rel_path = new_path.relative_path_from(dir).to_s
         | 
| 62 | 
            -
                      _queue_change(:dir, dir, rel_path, recursive: true)
         | 
| 63 | 
            -
                    end
         | 
| 64 | 
            -
                  end
         | 
| 52 | 
            +
                  def _process_changes(dirs)
         | 
| 53 | 
            +
                    dirs.each do |dir|
         | 
| 54 | 
            +
                      dir = Pathname.new(dir.sub(%r{/$}, ''))
         | 
| 65 55 |  | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 56 | 
            +
                      @callbacks.each do |watched_dir, callback|
         | 
| 57 | 
            +
                        if watched_dir.eql?(dir) || Listen::Directory.ascendant_of?(watched_dir, dir)
         | 
| 58 | 
            +
                          callback.call(dir)
         | 
| 59 | 
            +
                        end
         | 
| 60 | 
            +
                      end
         | 
| 61 | 
            +
                    end
         | 
| 71 62 | 
             
                  end
         | 
| 72 63 |  | 
| 73 | 
            -
                  def  | 
| 74 | 
            -
                     | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 77 | 
            -
             | 
| 78 | 
            -
                    end
         | 
| 64 | 
            +
                  def _process_event(dir, path)
         | 
| 65 | 
            +
                    Listen.logger.debug { "fsevent: processing path: #{path.inspect}" }
         | 
| 66 | 
            +
                    # TODO: does this preserve symlinks?
         | 
| 67 | 
            +
                    rel_path = path.relative_path_from(dir).to_s
         | 
| 68 | 
            +
                    _queue_change(:dir, dir, rel_path, recursive: true)
         | 
| 79 69 | 
             
                  end
         | 
| 80 70 |  | 
| 81 | 
            -
                  def  | 
| 82 | 
            -
                     | 
| 83 | 
            -
                     | 
| 84 | 
            -
                    workers
         | 
| 71 | 
            +
                  def _stop
         | 
| 72 | 
            +
                    @worker_thread&.kill
         | 
| 73 | 
            +
                    super
         | 
| 85 74 | 
             
                  end
         | 
| 86 75 | 
             
                end
         | 
| 87 76 | 
             
              end
         | 
    
        data/lib/listen/adapter/linux.rb
    CHANGED
    
    | @@ -1,45 +1,44 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Listen
         | 
| 2 4 | 
             
              module Adapter
         | 
| 3 5 | 
             
                # @see https://github.com/nex3/rb-inotify
         | 
| 4 6 | 
             
                class Linux < Base
         | 
| 5 | 
            -
                  OS_REGEXP = /linux/i
         | 
| 7 | 
            +
                  OS_REGEXP = /linux/i.freeze
         | 
| 6 8 |  | 
| 7 9 | 
             
                  DEFAULTS = {
         | 
| 8 10 | 
             
                    events: [
         | 
| 9 11 | 
             
                      :recursive,
         | 
| 10 12 | 
             
                      :attrib,
         | 
| 11 13 | 
             
                      :create,
         | 
| 14 | 
            +
                      :modify,
         | 
| 12 15 | 
             
                      :delete,
         | 
| 13 16 | 
             
                      :move,
         | 
| 14 17 | 
             
                      :close_write
         | 
| 15 18 | 
             
                    ],
         | 
| 16 19 | 
             
                    wait_for_delay: 0.1
         | 
| 17 | 
            -
                  }
         | 
| 20 | 
            +
                  }.freeze
         | 
| 18 21 |  | 
| 19 22 | 
             
                  private
         | 
| 20 23 |  | 
| 21 | 
            -
                   | 
| 22 | 
            -
                    '/ | 
| 23 | 
            -
             | 
| 24 | 
            -
                  INOTIFY_LIMIT_MESSAGE = <<-EOS.gsub(/^\s*/, '')
         | 
| 25 | 
            -
                    FATAL: Listen error: unable to monitor directories for changes.
         | 
| 26 | 
            -
                    Visit #{WIKI_URL} for info on how to fix this.
         | 
| 27 | 
            -
                  EOS
         | 
| 24 | 
            +
                  README_URL = 'https://github.com/guard/listen'\
         | 
| 25 | 
            +
                    '/blob/master/README.md#increasing-the-amount-of-inotify-watchers'
         | 
| 28 26 |  | 
| 29 27 | 
             
                  def _configure(directory, &callback)
         | 
| 30 28 | 
             
                    require 'rb-inotify'
         | 
| 31 29 | 
             
                    @worker ||= ::INotify::Notifier.new
         | 
| 32 30 | 
             
                    @worker.watch(directory.to_s, *options.events, &callback)
         | 
| 33 31 | 
             
                  rescue Errno::ENOSPC
         | 
| 34 | 
            -
                     | 
| 32 | 
            +
                    raise ::Listen::Error::INotifyMaxWatchesExceeded, <<~EOS
         | 
| 33 | 
            +
                      Unable to monitor directories for changes because iNotify max watches exceeded. See #{README_URL} .
         | 
| 34 | 
            +
                    EOS
         | 
| 35 35 | 
             
                  end
         | 
| 36 36 |  | 
| 37 37 | 
             
                  def _run
         | 
| 38 | 
            -
                    Thread.current[:listen_blocking_read_thread] = true
         | 
| 39 38 | 
             
                    @worker.run
         | 
| 40 | 
            -
                    Thread.current[:listen_blocking_read_thread] = false
         | 
| 41 39 | 
             
                  end
         | 
| 42 40 |  | 
| 41 | 
            +
                  # rubocop:disable Metrics/MethodLength
         | 
| 43 42 | 
             
                  def _process_event(dir, event)
         | 
| 44 43 | 
             
                    # NOTE: avoid using event.absolute_name since new API
         | 
| 45 44 | 
             
                    # will need to have a custom recursion implemented
         | 
| @@ -47,15 +46,13 @@ module Listen | |
| 47 46 | 
             
                    path = Pathname.new(event.watcher.path) + event.name
         | 
| 48 47 | 
             
                    rel_path = path.relative_path_from(dir).to_s
         | 
| 49 48 |  | 
| 50 | 
            -
                     | 
| 49 | 
            +
                    Listen.logger.debug { "inotify: #{rel_path} (#{event.flags.inspect})" }
         | 
| 51 50 |  | 
| 52 51 | 
             
                    if /1|true/ =~ ENV['LISTEN_GEM_SIMULATE_FSEVENT']
         | 
| 53 52 | 
             
                      if (event.flags & [:moved_to, :moved_from]) || _dir_event?(event)
         | 
| 54 53 | 
             
                        rel_path = path.dirname.relative_path_from(dir).to_s
         | 
| 55 | 
            -
                        _queue_change(:dir, dir, rel_path, {})
         | 
| 56 | 
            -
                      else
         | 
| 57 | 
            -
                        _queue_change(:dir, dir, rel_path, {})
         | 
| 58 54 | 
             
                      end
         | 
| 55 | 
            +
                      _queue_change(:dir, dir, rel_path, {})
         | 
| 59 56 | 
             
                      return
         | 
| 60 57 | 
             
                    end
         | 
| 61 58 |  | 
| @@ -74,6 +71,7 @@ module Listen | |
| 74 71 |  | 
| 75 72 | 
             
                    _queue_change(:file, dir, rel_path, params)
         | 
| 76 73 | 
             
                  end
         | 
| 74 | 
            +
                  # rubocop:enable Metrics/MethodLength
         | 
| 77 75 |  | 
| 78 76 | 
             
                  def _skip_event?(event)
         | 
| 79 77 | 
             
                    # Event on root directory
         | 
| @@ -101,7 +99,9 @@ module Listen | |
| 101 99 | 
             
                  end
         | 
| 102 100 |  | 
| 103 101 | 
             
                  def _stop
         | 
| 104 | 
            -
                    @worker | 
| 102 | 
            +
                    @worker&.close
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                    super
         | 
| 105 105 | 
             
                  end
         | 
| 106 106 | 
             
                end
         | 
| 107 107 | 
             
              end
         | 
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Listen
         | 
| 2 4 | 
             
              module Adapter
         | 
| 3 5 | 
             
                # Polling Adapter that works cross-platform and
         | 
| @@ -6,9 +8,9 @@ module Listen | |
| 6 8 | 
             
                # file IO than the other implementations.
         | 
| 7 9 | 
             
                #
         | 
| 8 10 | 
             
                class Polling < Base
         | 
| 9 | 
            -
                  OS_REGEXP =  | 
| 11 | 
            +
                  OS_REGEXP = //.freeze # match every OS
         | 
| 10 12 |  | 
| 11 | 
            -
                  DEFAULTS = { latency: 1.0, wait_for_delay: 0.05 }
         | 
| 13 | 
            +
                  DEFAULTS = { latency: 1.0, wait_for_delay: 0.05 }.freeze
         | 
| 12 14 |  | 
| 13 15 | 
             
                  private
         | 
| 14 16 |  | 
| @@ -19,12 +21,13 @@ module Listen | |
| 19 21 |  | 
| 20 22 | 
             
                  def _run
         | 
| 21 23 | 
             
                    loop do
         | 
| 22 | 
            -
                      start =  | 
| 24 | 
            +
                      start = MonotonicTime.now
         | 
| 23 25 | 
             
                      @polling_callbacks.each do |callback|
         | 
| 24 26 | 
             
                        callback.call(nil)
         | 
| 25 | 
            -
                        nap_time = options.latency - ( | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 27 | 
            +
                        if (nap_time = options.latency - (MonotonicTime.now - start)) > 0
         | 
| 28 | 
            +
                          # TODO: warn if nap_time is negative (polling too slow)
         | 
| 29 | 
            +
                          sleep(nap_time)
         | 
| 30 | 
            +
                        end
         | 
| 28 31 | 
             
                      end
         | 
| 29 32 | 
             
                    end
         | 
| 30 33 | 
             
                  end
         | 
| @@ -1,9 +1,11 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Listen
         | 
| 2 4 | 
             
              module Adapter
         | 
| 3 5 | 
             
                # Adapter implementation for Windows `wdm`.
         | 
| 4 6 | 
             
                #
         | 
| 5 7 | 
             
                class Windows < Base
         | 
| 6 | 
            -
                  OS_REGEXP = /mswin|mingw|cygwin/i
         | 
| 8 | 
            +
                  OS_REGEXP = /mswin|mingw|cygwin/i.freeze
         | 
| 7 9 |  | 
| 8 10 | 
             
                  BUNDLER_DECLARE_GEM = <<-EOS.gsub(/^ {6}/, '')
         | 
| 9 11 | 
             
                    Please add the following to your Gemfile to avoid polling for changes:
         | 
| @@ -15,8 +17,8 @@ module Listen | |
| 15 17 | 
             
                    require 'wdm'
         | 
| 16 18 | 
             
                    true
         | 
| 17 19 | 
             
                  rescue LoadError
         | 
| 18 | 
            -
                     | 
| 19 | 
            -
             | 
| 20 | 
            +
                    Listen.logger.debug format('wdm - load failed: %s:%s', $ERROR_INFO,
         | 
| 21 | 
            +
                                               $ERROR_POSITION * "\n")
         | 
| 20 22 |  | 
| 21 23 | 
             
                    Kernel.warn BUNDLER_DECLARE_GEM
         | 
| 22 24 | 
             
                    false
         | 
| @@ -24,21 +26,20 @@ module Listen | |
| 24 26 |  | 
| 25 27 | 
             
                  private
         | 
| 26 28 |  | 
| 27 | 
            -
                  def _configure(dir | 
| 29 | 
            +
                  def _configure(dir)
         | 
| 28 30 | 
             
                    require 'wdm'
         | 
| 29 | 
            -
                     | 
| 31 | 
            +
                    Listen.logger.debug 'wdm - starting...'
         | 
| 30 32 | 
             
                    @worker ||= WDM::Monitor.new
         | 
| 31 33 | 
             
                    @worker.watch_recursively(dir.to_s, :files) do |change|
         | 
| 32 | 
            -
                       | 
| 34 | 
            +
                      yield([:file, change])
         | 
| 33 35 | 
             
                    end
         | 
| 34 36 |  | 
| 35 37 | 
             
                    @worker.watch_recursively(dir.to_s, :directories) do |change|
         | 
| 36 | 
            -
                       | 
| 38 | 
            +
                      yield([:dir, change])
         | 
| 37 39 | 
             
                    end
         | 
| 38 40 |  | 
| 39 | 
            -
                     | 
| 40 | 
            -
             | 
| 41 | 
            -
                      callback.call([:attr, change])
         | 
| 41 | 
            +
                    @worker.watch_recursively(dir.to_s, :attributes, :last_write) do |change|
         | 
| 42 | 
            +
                      yield([:attr, change])
         | 
| 42 43 | 
             
                    end
         | 
| 43 44 | 
             
                  end
         | 
| 44 45 |  | 
| @@ -46,8 +47,9 @@ module Listen | |
| 46 47 | 
             
                    @worker.run!
         | 
| 47 48 | 
             
                  end
         | 
| 48 49 |  | 
| 50 | 
            +
                  # rubocop:disable Metrics/MethodLength
         | 
| 49 51 | 
             
                  def _process_event(dir, event)
         | 
| 50 | 
            -
                     | 
| 52 | 
            +
                    Listen.logger.debug "wdm - callback: #{event.inspect}"
         | 
| 51 53 |  | 
| 52 54 | 
             
                    type, change = event
         | 
| 53 55 |  | 
| @@ -65,12 +67,12 @@ module Listen | |
| 65 67 | 
             
                        _queue_change(:file, dir, rel_path, options)
         | 
| 66 68 | 
             
                      end
         | 
| 67 69 | 
             
                    when :dir
         | 
| 68 | 
            -
                       | 
| 70 | 
            +
                      case change.type
         | 
| 71 | 
            +
                      when :removed
         | 
| 69 72 | 
             
                        # TODO: check if watched dir?
         | 
| 70 73 | 
             
                        _queue_change(:dir, dir, Pathname(rel_path).dirname.to_s, {})
         | 
| 71 | 
            -
                       | 
| 74 | 
            +
                      when :added
         | 
| 72 75 | 
             
                        _queue_change(:dir, dir, rel_path, {})
         | 
| 73 | 
            -
                      else
         | 
| 74 76 | 
             
                        # do nothing - changed directory means either:
         | 
| 75 77 | 
             
                        #   - removed subdirs (handled above)
         | 
| 76 78 | 
             
                        #   - added subdirs (handled above)
         | 
| @@ -79,20 +81,15 @@ module Listen | |
| 79 81 | 
             
                        # so what's left?
         | 
| 80 82 | 
             
                      end
         | 
| 81 83 | 
             
                    end
         | 
| 82 | 
            -
                  rescue
         | 
| 83 | 
            -
                    details = event.inspect
         | 
| 84 | 
            -
                    _log :error, format('wdm - callback (%): %s:%s', details, $ERROR_INFO,
         | 
| 85 | 
            -
                                        $ERROR_POSITION * "\n")
         | 
| 86 | 
            -
                    raise
         | 
| 87 84 | 
             
                  end
         | 
| 85 | 
            +
                  # rubocop:enable Metrics/MethodLength
         | 
| 88 86 |  | 
| 89 87 | 
             
                  def _change(type)
         | 
| 90 88 | 
             
                    { modified: [:modified, :attrib], # TODO: is attrib really passed?
         | 
| 91 89 | 
             
                      added:    [:added, :renamed_new_file],
         | 
| 92 | 
            -
                      removed:  [:removed, :renamed_old_file] }. | 
| 93 | 
            -
                       | 
| 90 | 
            +
                      removed:  [:removed, :renamed_old_file] }.find do |change, types|
         | 
| 91 | 
            +
                      types.include?(type) and break change
         | 
| 94 92 | 
             
                    end
         | 
| 95 | 
            -
                    nil
         | 
| 96 93 | 
             
                  end
         | 
| 97 94 | 
             
                end
         | 
| 98 95 | 
             
              end
         | 
    
        data/lib/listen/adapter.rb
    CHANGED
    
    | @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'listen/adapter/base'
         | 
| 2 4 | 
             
            require 'listen/adapter/bsd'
         | 
| 3 5 | 
             
            require 'listen/adapter/darwin'
         | 
| @@ -7,37 +9,35 @@ require 'listen/adapter/windows' | |
| 7 9 |  | 
| 8 10 | 
             
            module Listen
         | 
| 9 11 | 
             
              module Adapter
         | 
| 10 | 
            -
                OPTIMIZED_ADAPTERS = [Darwin, Linux, BSD, Windows]
         | 
| 12 | 
            +
                OPTIMIZED_ADAPTERS = [Darwin, Linux, BSD, Windows].freeze
         | 
| 11 13 | 
             
                POLLING_FALLBACK_MESSAGE = 'Listen will be polling for changes.'\
         | 
| 12 14 | 
             
                  'Learn more at https://github.com/guard/listen#listen-adapters.'
         | 
| 13 15 |  | 
| 14 | 
            -
                 | 
| 15 | 
            -
                   | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
                   | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 16 | 
            +
                class << self
         | 
| 17 | 
            +
                  def select(options = {})
         | 
| 18 | 
            +
                    Listen.logger.debug 'Adapter: considering polling ...'
         | 
| 19 | 
            +
                    return Polling if options[:force_polling]
         | 
| 20 | 
            +
                    Listen.logger.debug 'Adapter: considering optimized backend...'
         | 
| 21 | 
            +
                    return _usable_adapter_class if _usable_adapter_class
         | 
| 22 | 
            +
                    Listen.logger.debug 'Adapter: falling back to polling...'
         | 
| 23 | 
            +
                    _warn_polling_fallback(options)
         | 
| 24 | 
            +
                    Polling
         | 
| 25 | 
            +
                  rescue
         | 
| 26 | 
            +
                    Listen.logger.warn format('Adapter: failed: %s:%s', $ERROR_POSITION.inspect,
         | 
| 27 | 
            +
                                              $ERROR_POSITION * "\n")
         | 
| 28 | 
            +
                    raise
         | 
| 29 | 
            +
                  end
         | 
| 27 30 |  | 
| 28 | 
            -
             | 
| 31 | 
            +
                  private
         | 
| 29 32 |  | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
                def self._warn_polling_fallback(options)
         | 
| 35 | 
            -
                  msg = options.fetch(:polling_fallback_message, POLLING_FALLBACK_MESSAGE)
         | 
| 36 | 
            -
                  Kernel.warn "[Listen warning]:\n  #{msg}" if msg
         | 
| 37 | 
            -
                end
         | 
| 33 | 
            +
                  def _usable_adapter_class
         | 
| 34 | 
            +
                    OPTIMIZED_ADAPTERS.find(&:usable?)
         | 
| 35 | 
            +
                  end
         | 
| 38 36 |  | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 37 | 
            +
                  def _warn_polling_fallback(options)
         | 
| 38 | 
            +
                    msg = options.fetch(:polling_fallback_message, POLLING_FALLBACK_MESSAGE)
         | 
| 39 | 
            +
                    Kernel.warn "[Listen warning]:\n  #{msg}" if msg
         | 
| 40 | 
            +
                  end
         | 
| 41 41 | 
             
                end
         | 
| 42 42 | 
             
              end
         | 
| 43 43 | 
             
            end
         | 
    
        data/lib/listen/backend.rb
    CHANGED
    
    | @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'listen/adapter'
         | 
| 2 4 | 
             
            require 'listen/adapter/base'
         | 
| 3 5 | 
             
            require 'listen/adapter/config'
         | 
| @@ -8,6 +10,8 @@ require 'forwardable' | |
| 8 10 | 
             
            # from exploding with huge test setup blocks
         | 
| 9 11 | 
             
            module Listen
         | 
| 10 12 | 
             
              class Backend
         | 
| 13 | 
            +
                extend Forwardable
         | 
| 14 | 
            +
             | 
| 11 15 | 
             
                def initialize(directories, queue, silencer, config)
         | 
| 12 16 | 
             
                  adapter_select_opts = config.adapter_select_options
         | 
| 13 17 |  | 
| @@ -24,17 +28,10 @@ module Listen | |
| 24 28 | 
             
                  @adapter = adapter_class.new(aconfig)
         | 
| 25 29 | 
             
                end
         | 
| 26 30 |  | 
| 27 | 
            -
                 | 
| 28 | 
            -
             | 
| 29 | 
            -
                end
         | 
| 30 | 
            -
             | 
| 31 | 
            -
                def stop
         | 
| 32 | 
            -
                  adapter.stop
         | 
| 33 | 
            -
                end
         | 
| 31 | 
            +
                delegate start: :adapter
         | 
| 32 | 
            +
                delegate stop: :adapter
         | 
| 34 33 |  | 
| 35 | 
            -
                 | 
| 36 | 
            -
                  @min_delay_between_events
         | 
| 37 | 
            -
                end
         | 
| 34 | 
            +
                attr_reader :min_delay_between_events
         | 
| 38 35 |  | 
| 39 36 | 
             
                private
         | 
| 40 37 |  |