Class: ActiveSupport::EventedFileUpdateChecker

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

Overview

Allows you to “listen” to changes in a file system. The evented file updater does not hit disk when checking for updates instead it uses platform specific file system events to trigger a change in state.

The file checker takes an array of files to watch or a hash specifying directories and file extensions to watch. It also takes a block that is called when EventedFileUpdateChecker#execute is run or when EventedFileUpdateChecker#execute_if_updated is run and there have been changes to the file system.

Note: Forking will cause the first call to `updated?` to return `true`.

Example:

checker = ActiveSupport::EventedFileUpdateChecker.new(["/tmp/foo"]) { puts "changed" }
checker.updated?
# => false
checker.execute_if_updated
# => nil

FileUtils.touch("/tmp/foo")

checker.updated?
# => true
checker.execute_if_updated
# => "changed"

Defined Under Namespace

Classes: PathHelper

Instance Method Summary collapse

Constructor Details

#initialize(files, dirs = {}, &block) ⇒ EventedFileUpdateChecker

:nodoc: all



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
# File 'lib/active_support/evented_file_update_checker.rb', line 36

def initialize(files, dirs = {}, &block)
  unless block
    raise ArgumentError, "A block is required to initialize an EventedFileUpdateChecker"
  end

  @ph    = PathHelper.new
  @files = files.map { |f| @ph.xpath(f) }.to_set

  @dirs = {}
  dirs.each do |dir, exts|
    @dirs[@ph.xpath(dir)] = Array(exts).map { |ext| @ph.normalize_extension(ext) }
  end

  @block      = block
  @updated    = Concurrent::AtomicBoolean.new(false)
  @lcsp       = @ph.longest_common_subpath(@dirs.keys)
  @pid        = Process.pid
  @boot_mutex = Mutex.new

  dtw = directories_to_watch
  @dtw, @missing = dtw.partition(&:exist?)

  if @dtw.any?
    # Loading listen triggers warnings. These are originated by a legit
    # usage of attr_* macros for private attributes, but adds a lot of noise
    # to our test suite. Thus, we lazy load it and disable warnings locally.
    silence_warnings do
      require "listen"
    rescue LoadError => e
      raise LoadError, "Could not load the 'listen' gem. Add `gem 'listen'` to the development group of your Gemfile", e.backtrace
    end
  end
  boot!
end

Instance Method Details

#executeObject



95
96
97
98
# File 'lib/active_support/evented_file_update_checker.rb', line 95

def execute
  @updated.make_false
  @block.call
end

#execute_if_updatedObject



100
101
102
103
104
105
106
# File 'lib/active_support/evented_file_update_checker.rb', line 100

def execute_if_updated
  if updated?
    yield if block_given?
    execute
    true
  end
end

#updated?Boolean

Returns:

  • (Boolean)


71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/active_support/evented_file_update_checker.rb', line 71

def updated?
  @boot_mutex.synchronize do
    if @pid != Process.pid
      boot!
      @pid = Process.pid
      @updated.make_true
    end
  end

  if @missing.any?(&:exist?)
    @boot_mutex.synchronize do
      appeared, @missing = @missing.partition(&:exist?)
      shutdown!

      @dtw += appeared
      boot!

      @updated.make_true
    end
  end

  @updated.true?
end