Class: Philiprehberger::FileWatcher::Watcher

Inherits:
Object
  • Object
show all
Defined in:
lib/philiprehberger/file_watcher/watcher.rb

Overview

Watches file system paths for changes using polling.

Detects created, modified, and deleted files by comparing mtime snapshots at a configurable interval.

Instance Method Summary collapse

Constructor Details

#initialize(paths, interval: 1.0, glob: '**/*', exclude: [], debounce: nil) ⇒ Watcher

Returns a new instance of Watcher.

Parameters:

  • paths (Array<String>, String)

    directories or files to watch

  • interval (Float) (defaults to: 1.0)

    polling interval in seconds (default: 1.0)

  • glob (String) (defaults to: '**/*')

    glob pattern for matching files (default: “*/”)

  • exclude (Array<String>) (defaults to: [])

    glob patterns to exclude from watching (default: [])

  • debounce (Float, nil) (defaults to: nil)

    debounce interval in seconds (default: nil)



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/philiprehberger/file_watcher/watcher.rb', line 17

def initialize(paths, interval: 1.0, glob: '**/*', exclude: [], debounce: nil)
  @paths = Array(paths)
  @interval = interval
  @glob = glob
  @exclude = Array(exclude)
  @debounce = debounce
  @callbacks = { created: [], modified: [], deleted: [], any: [], error: [], batch: [] }
  @mutex = Mutex.new
  @thread = nil
  @running = false
  @paused = false
  @snapshot = {}
  @pending_debounce = {}
  @total_changes = 0
  @last_change_at = nil
end

Instance Method Details

#on(type) {|Change| ... } ⇒ self

Register a callback for a specific change type.

Parameters:

  • type (Symbol)

    one of :created, :modified, :deleted, :any, :error, or :batch

Yields:

  • (Change)

    called when a matching change is detected

  • (Exception, String)

    for :error, called with (exception, path)

  • (Array<Change>)

    for :batch, called with all changes from one polling cycle

Returns:

  • (self)

Raises:

  • (ArgumentError)

    if the type is not valid



42
43
44
45
46
47
48
49
50
# File 'lib/philiprehberger/file_watcher/watcher.rb', line 42

def on(type, &block)
  unless @callbacks.key?(type)
    raise ArgumentError,
          "invalid event type: #{type.inspect} (must be one of #{@callbacks.keys.join(', ')})"
  end

  @mutex.synchronize { @callbacks[type] << block }
  self
end

#pauseself

Pause change detection. The polling thread stays alive but skips detection.

Returns:

  • (self)


87
88
89
90
# File 'lib/philiprehberger/file_watcher/watcher.rb', line 87

def pause
  @mutex.synchronize { @paused = true }
  self
end

#paused?Boolean

Returns true if the watcher is currently paused.

Returns:

  • (Boolean)

    true if the watcher is currently paused



107
108
109
# File 'lib/philiprehberger/file_watcher/watcher.rb', line 107

def paused?
  @mutex.synchronize { @paused }
end

#resumeself

Resume change detection with a fresh snapshot.

Changes that occurred while paused are silently ignored.

Returns:

  • (self)


97
98
99
100
101
102
103
104
# File 'lib/philiprehberger/file_watcher/watcher.rb', line 97

def resume
  @mutex.synchronize do
    @snapshot = take_snapshot
    @pending_debounce.clear
    @paused = false
  end
  self
end

#running?Boolean

Returns true if the watcher is currently running.

Returns:

  • (Boolean)

    true if the watcher is currently running



80
81
82
# File 'lib/philiprehberger/file_watcher/watcher.rb', line 80

def running?
  @mutex.synchronize { @running }
end

#snapshotHash{String => Hash}

Return a hash of all currently tracked files with their mtime and size.

Returns:

  • (Hash{String => Hash})

    mapping of path to size:



114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/philiprehberger/file_watcher/watcher.rb', line 114

def snapshot
  @mutex.synchronize do
    @snapshot.each_with_object({}) do |(path, mtime), result|
      size = begin
        File.size(path)
      rescue StandardError
        0
      end
      result[path] = { mtime: mtime, size: size }
    end
  end
end

#startself

Start watching in a background thread.

Takes an initial snapshot and begins polling for changes.

Returns:

  • (self)


57
58
59
60
61
62
63
64
65
66
# File 'lib/philiprehberger/file_watcher/watcher.rb', line 57

def start
  @mutex.synchronize do
    return self if @running

    @running = true
    @snapshot = take_snapshot
    @thread = Thread.new { poll_loop }
  end
  self
end

#statsHash

Return runtime statistics for the watcher.

The returned hash is a fresh copy on each call and safe to mutate.

Returns:

  • (Hash)

    with keys :tracked_files (Integer), :total_changes (Integer), and :last_change_at (Time or nil)



133
134
135
136
137
138
139
140
141
# File 'lib/philiprehberger/file_watcher/watcher.rb', line 133

def stats
  @mutex.synchronize do
    {
      tracked_files: @snapshot.size,
      total_changes: @total_changes,
      last_change_at: @last_change_at
    }
  end
end

#stopself

Stop the watcher and join the background thread.

Returns:

  • (self)


71
72
73
74
75
76
77
# File 'lib/philiprehberger/file_watcher/watcher.rb', line 71

def stop
  @mutex.synchronize { @running = false }
  @thread&.join
  @thread = nil
  flush_debounced_changes
  self
end