| module Listen |
| module Adapter |
| # @see https://github.com/nex3/rb-inotify |
| class Linux < Base |
| OS_REGEXP = /linux/i |
| |
| DEFAULTS = { |
| events: [ |
| :recursive, |
| :attrib, |
| :create, |
| :delete, |
| :move, |
| :close_write |
| ], |
| wait_for_delay: 0.1 |
| } |
| |
| private |
| |
| WIKI_URL = 'https://github.com/guard/listen'\ |
| '/wiki/Increasing-the-amount-of-inotify-watchers' |
| |
| INOTIFY_LIMIT_MESSAGE = <<-EOS.gsub(/^\s*/, '') |
| FATAL: Listen error: unable to monitor directories for changes. |
| Visit #{WIKI_URL} for info on how to fix this. |
| EOS |
| |
| def _configure(directory, &callback) |
| require 'rb-inotify' |
| @worker ||= ::INotify::Notifier.new |
| @worker.watch(directory.to_s, *options.events, &callback) |
| rescue Errno::ENOSPC |
| abort(INOTIFY_LIMIT_MESSAGE) |
| end |
| |
| def _run |
| Thread.current[:listen_blocking_read_thread] = true |
| @worker.run |
| Thread.current[:listen_blocking_read_thread] = false |
| end |
| |
| def _process_event(dir, event) |
| # NOTE: avoid using event.absolute_name since new API |
| # will need to have a custom recursion implemented |
| # to properly match events to configured directories |
| path = Pathname.new(event.watcher.path) + event.name |
| rel_path = path.relative_path_from(dir).to_s |
| |
| _log(:debug) { "inotify: #{rel_path} (#{event.flags.inspect})" } |
| |
| if /1|true/ =~ ENV['LISTEN_GEM_SIMULATE_FSEVENT'] |
| if (event.flags & [:moved_to, :moved_from]) || _dir_event?(event) |
| rel_path = path.dirname.relative_path_from(dir).to_s |
| _queue_change(:dir, dir, rel_path, {}) |
| else |
| _queue_change(:dir, dir, rel_path, {}) |
| end |
| return |
| end |
| |
| return if _skip_event?(event) |
| |
| cookie_params = event.cookie.zero? ? {} : { cookie: event.cookie } |
| |
| # Note: don't pass options to force rescanning the directory, so we can |
| # detect moving/deleting a whole tree |
| if _dir_event?(event) |
| _queue_change(:dir, dir, rel_path, cookie_params) |
| return |
| end |
| |
| params = cookie_params.merge(change: _change(event.flags)) |
| |
| _queue_change(:file, dir, rel_path, params) |
| end |
| |
| def _skip_event?(event) |
| # Event on root directory |
| return true if event.name == '' |
| # INotify reports changes to files inside directories as events |
| # on the directories themselves too. |
| # |
| # @see http://linux.die.net/man/7/inotify |
| _dir_event?(event) && (event.flags & [:close, :modify]).any? |
| end |
| |
| def _change(event_flags) |
| { modified: [:attrib, :close_write], |
| moved_to: [:moved_to], |
| moved_from: [:moved_from], |
| added: [:create], |
| removed: [:delete] }.each do |change, flags| |
| return change unless (flags & event_flags).empty? |
| end |
| nil |
| end |
| |
| def _dir_event?(event) |
| event.flags.include?(:isdir) |
| end |
| |
| def _stop |
| @worker && @worker.close |
| end |
| end |
| end |
| end |