File: bsd.rb

package info (click to toggle)
ruby-listen 3.9.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 544 kB
  • sloc: ruby: 5,033; makefile: 9
file content (107 lines) | stat: -rw-r--r-- 3,031 bytes parent folder | download
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
# frozen_string_literal: true

# Listener implementation for BSD's `kqueue`.
# @see http://www.freebsd.org/cgi/man.cgi?query=kqueue
# @see https://github.com/mat813/rb-kqueue/blob/master/lib/rb-kqueue/queue.rb
#
module Listen
  module Adapter
    class BSD < Base
      OS_REGEXP = /bsd|dragonfly/i.freeze

      DEFAULTS = {
        events: [
          :delete,
          :write,
          :extend,
          :attrib,
          :rename
          # :link, :revoke
        ]
      }.freeze

      BUNDLER_DECLARE_GEM = <<-EOS.gsub(/^ {6}/, '')
        Please add the following to your Gemfile to avoid polling for changes:
          require 'rbconfig'
          if RbConfig::CONFIG['target_os'] =~ /#{OS_REGEXP}/
            gem 'rb-kqueue', '>= 0.2'
          end
      EOS

      def self.usable?
        return false unless super
        require 'rb-kqueue'
        require 'find'
        true
      rescue LoadError
        Listen.adapter_warn(BUNDLER_DECLARE_GEM)
        false
      end

      private

      def _configure(directory, &callback)
        @worker ||= KQueue::Queue.new
        @callback = callback
        # use Record to make a snapshot of dir, so we
        # can detect new files
        _find(directory.to_s) { |path| _watch_file(path, @worker) }
      end

      def _run
        @worker.run
      end

      def _process_event(dir, event)
        full_path = _event_path(event)
        if full_path.directory?
          # Force dir content tracking to kick in, or we won't have
          # names of added files
          _queue_change(:dir, dir, '.', recursive: true)
        elsif full_path.exist?
          path = full_path.relative_path_from(dir)
          _queue_change(:file, dir, path.to_s, change: _change(event.flags))
        end

        # If it is a directory, and it has a write flag, it means a
        # file has been added so find out which and deal with it.
        # No need to check for removed files, kqueue will forget them
        # when the vfs does.
        _watch_for_new_file(event) if full_path.directory?
      end

      def _change(event_flags)
        { modified: [:attrib, :extend],
          added:    [:write],
          removed:  [:rename, :delete] }.each do |change, flags|
          return change unless (flags & event_flags).empty?
        end
        nil
      end

      def _event_path(event)
        Pathname.new(event.watcher.path)
      end

      def _watch_for_new_file(event)
        queue = event.watcher.queue
        _find(_event_path(event).to_s) do |file_path|
          unless queue.watchers.find { |_, v| v.path == file_path.to_s }
            _watch_file(file_path, queue)
          end
        end
      end

      def _watch_file(path, queue)
        queue.watch_file(path, *options.events, &@callback)
      rescue Errno::ENOENT => e
        Listen.logger.warn "kqueue: watch file failed: #{e.message}"
      end

      # Quick rubocop workaround
      def _find(*paths, &block)
        Find.send(:find, *paths, &block)
      end
    end
  end
end