listen 3.3.3 → 3.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +2 -2
- data/README.md +122 -19
- data/lib/listen/adapter/base.rb +3 -3
- data/lib/listen/adapter/linux.rb +5 -8
- data/lib/listen/adapter/polling.rb +5 -4
- data/lib/listen/directory.rb +1 -1
- data/lib/listen/error.rb +11 -0
- data/lib/listen/event/config.rb +0 -4
- data/lib/listen/event/loop.rb +5 -4
- data/lib/listen/event/processor.rb +7 -9
- data/lib/listen/file.rb +7 -5
- data/lib/listen/fsm.rb +3 -0
- data/lib/listen/monotonic_time.rb +27 -0
- data/lib/listen/record/symlink_detector.rb +6 -6
- data/lib/listen/record.rb +25 -31
- data/lib/listen/silencer.rb +16 -14
- data/lib/listen/thread.rb +1 -1
- data/lib/listen/version.rb +1 -1
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 800a9d0af997cb48bc330b0aeaecdcb39b53e9bdeb2dbed6c08fd03b237a3d36
|
4
|
+
data.tar.gz: 5caa12c52d09e7447d0f1b43fddadf03e4e33ae4c611bd0ed7d367e3cd3a280b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8fa9b8758f30b8ececde07ca321a23fb02c9cfbec59e587011b1598d297f8a0a451728fb394e0bc1fa182e7ef40e4598bfa82b035bdccc29f6375b5df95dda6f
|
7
|
+
data.tar.gz: 8b94d5ab7085f27382cfca52d32ff8717c3d17f14ed3632a96853e8e4bcd2174bbc8f5f8f0ff3709f739b13c440fb87a82540bb0ae784395896fb0513837895a
|
data/CONTRIBUTING.md
CHANGED
@@ -4,7 +4,7 @@ Contribute to Listen
|
|
4
4
|
File an issue
|
5
5
|
-------------
|
6
6
|
|
7
|
-
If you haven't already, first see [TROUBLESHOOTING](https://github.com/guard/listen/
|
7
|
+
If you haven't already, first see [TROUBLESHOOTING](https://github.com/guard/listen/blob/master/README.md#Issues-and-Troubleshooting) for known issues, solutions and workarounds.
|
8
8
|
|
9
9
|
You can report bugs and feature requests to [GitHub Issues](https://github.com/guard/listen/issues).
|
10
10
|
|
@@ -16,7 +16,7 @@ Try to figure out where the issue belongs to: Is it an issue with Listen itself
|
|
16
16
|
|
17
17
|
**It's most likely that your bug gets resolved faster if you provide as much information as possible!**
|
18
18
|
|
19
|
-
The MOST useful information is debugging output from Listen (`LISTEN_GEM_DEBUGGING=1`) - see [TROUBLESHOOTING](https://github.com/guard/listen/
|
19
|
+
The MOST useful information is debugging output from Listen (`LISTEN_GEM_DEBUGGING=1`) - see [TROUBLESHOOTING](https://github.com/guard/listen/blob/master/README.md#Issues-and-Troubleshooting) for details.
|
20
20
|
|
21
21
|
|
22
22
|
Development
|
data/README.md
CHANGED
@@ -2,8 +2,6 @@
|
|
2
2
|
|
3
3
|
The `listen` gem listens to file modifications and notifies you about the changes.
|
4
4
|
|
5
|
-
:exclamation: `Listen` is currently accepting more maintainers. Please [read this](https://github.com/guard/guard/wiki/Maintainers) if you're interested in joining the team.
|
6
|
-
|
7
5
|
[![Development Status](https://github.com/guard/listen/workflows/Development/badge.svg)](https://github.com/guard/listen/actions?workflow=Development)
|
8
6
|
[![Gem Version](https://badge.fury.io/rb/listen.svg)](http://badge.fury.io/rb/listen)
|
9
7
|
[![Code Climate](https://codeclimate.com/github/guard/listen.svg)](https://codeclimate.com/github/guard/listen)
|
@@ -22,15 +20,13 @@ The `listen` gem listens to file modifications and notifies you about the change
|
|
22
20
|
|
23
21
|
* Limited support for symlinked directories ([#279](https://github.com/guard/listen/issues/279)):
|
24
22
|
* Symlinks are always followed ([#25](https://github.com/guard/listen/issues/25)).
|
25
|
-
* Symlinked directories pointing within a watched directory are not supported ([#273](https://github.com/guard/listen/pull/273)
|
23
|
+
* Symlinked directories pointing within a watched directory are not supported ([#273](https://github.com/guard/listen/pull/273).
|
26
24
|
* No directory/adapter-specific configuration options.
|
27
25
|
* Support for plugins planned for future.
|
28
26
|
* TCP functionality was removed in `listen` [3.0.0](https://github.com/guard/listen/releases/tag/v3.0.0) ([#319](https://github.com/guard/listen/issues/319), [#218](https://github.com/guard/listen/issues/218)). There are plans to extract this feature to separate gems ([#258](https://github.com/guard/listen/issues/258)), until this is finished, you can use by locking the `listen` gem to version `'~> 2.10'`.
|
29
27
|
* Some filesystems won't work without polling (VM/Vagrant Shared folders, NFS, Samba, sshfs, etc.).
|
30
|
-
* Specs suite on JRuby and Rubinius aren't reliable on Travis CI, but should work.
|
31
28
|
* Windows and \*BSD adapter aren't continuously and automatically tested.
|
32
29
|
* OSX adapter has some performance limitations ([#342](https://github.com/guard/listen/issues/342)).
|
33
|
-
* FreeBSD users need patched version of rb-kqueue (as of 2020/11). See #475 for the issue, mat813/rb-kqueue#12 for the patch, and Bug 250432 in bugzilla.
|
34
30
|
* Listeners do not notify across forked processes, if you wish for multiple processes to receive change notifications you must [listen inside of each process](https://github.com/guard/listen/issues/398#issuecomment-223957952).
|
35
31
|
|
36
32
|
Pull requests or help is very welcome for these.
|
@@ -40,7 +36,7 @@ Pull requests or help is very welcome for these.
|
|
40
36
|
The simplest way to install `listen` is to use [Bundler](http://bundler.io).
|
41
37
|
|
42
38
|
```ruby
|
43
|
-
gem 'listen'
|
39
|
+
gem 'listen'
|
44
40
|
```
|
45
41
|
|
46
42
|
## Complete Example
|
@@ -101,23 +97,23 @@ The callback receives **three** array parameters: `modified`, `added` and `remov
|
|
101
97
|
Each of these three is always an array with 0 or more entries.
|
102
98
|
Each array entry is an absolute path.
|
103
99
|
|
104
|
-
### Pause /
|
100
|
+
### Pause / start / stop
|
105
101
|
|
106
|
-
Listeners can also be easily paused
|
102
|
+
Listeners can also be easily paused and later un-paused with start:
|
107
103
|
|
108
104
|
``` ruby
|
109
105
|
listener = Listen.to('dir/path/to/listen') { |modified, added, removed| puts 'handle changes here...' }
|
110
106
|
|
111
107
|
listener.start
|
112
|
-
listener.paused?
|
108
|
+
listener.paused? # => false
|
113
109
|
listener.processing? # => true
|
114
110
|
|
115
|
-
listener.pause
|
116
|
-
listener.paused?
|
111
|
+
listener.pause # stops processing changes (but keeps on collecting them)
|
112
|
+
listener.paused? # => true
|
117
113
|
listener.processing? # => false
|
118
114
|
|
119
|
-
listener.
|
120
|
-
listener.stop
|
115
|
+
listener.start # resumes processing changes
|
116
|
+
listener.stop # stop both listening to changes and processing them
|
121
117
|
```
|
122
118
|
|
123
119
|
Note: While paused, `listen` keeps on collecting changes in the background - to clear them, call `stop`.
|
@@ -126,7 +122,7 @@ listener.stop # stop both listening to changes and processing them
|
|
126
122
|
|
127
123
|
### Ignore / ignore!
|
128
124
|
|
129
|
-
`Listen` ignores some directories and extensions by default (See
|
125
|
+
`Listen` ignores some directories and extensions by default (See DEFAULT_IGNORED_FILES and DEFAULT_IGNORED_EXTENSIONS in Listen::Silencer).
|
130
126
|
You can add ignoring patterns with the `ignore` option/method or overwrite default with `ignore!` option/method.
|
131
127
|
|
132
128
|
``` ruby
|
@@ -161,7 +157,7 @@ All the following options can be set through the `Listen.to` after the directory
|
|
161
157
|
|
162
158
|
```ruby
|
163
159
|
ignore: [%r{/foo/bar}, /\.pid$/, /\.coffee$/] # Ignore a list of paths
|
164
|
-
# default: See
|
160
|
+
# default: See DEFAULT_IGNORED_FILES and DEFAULT_IGNORED_EXTENSIONS in Listen::Silencer
|
165
161
|
|
166
162
|
ignore!: %r{/foo/bar} # Same as ignore options, but overwrite default ignored paths.
|
167
163
|
|
@@ -240,7 +236,7 @@ If you are on Windows, it's recommended to use the [`wdm`](https://github.com/Ma
|
|
240
236
|
Please add the following to your Gemfile:
|
241
237
|
|
242
238
|
```ruby
|
243
|
-
gem 'wdm', '>= 0.1.0'
|
239
|
+
gem 'wdm', '>= 0.1.0', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
|
244
240
|
```
|
245
241
|
|
246
242
|
### On \*BSD
|
@@ -259,7 +255,82 @@ end
|
|
259
255
|
|
260
256
|
### Getting the [polling fallback message](#options)?
|
261
257
|
|
262
|
-
|
258
|
+
If you see:
|
259
|
+
```
|
260
|
+
Listen will be polling for changes.
|
261
|
+
```
|
262
|
+
|
263
|
+
This means the Listen gem can’t find an optimized adapter. Typically this is caused by:
|
264
|
+
|
265
|
+
- You’re on Windows and WDM gem isn’t installed.
|
266
|
+
- You’re running the app without Bundler or RubyGems.
|
267
|
+
- Using Sass which includes an ancient (the “dinosaur” type of ancient) version of the Listen gem.
|
268
|
+
|
269
|
+
Possible solutions:
|
270
|
+
|
271
|
+
1. Suppress the message by using the :force_polling option. Or, you could just ignore the message since it’s harmless.
|
272
|
+
2. Windows users: Install the WDM gem.
|
273
|
+
3. Upgrade Ruby (use RubyInstaller for Windows or RVM/rbenv for Mac) and RubyGems.
|
274
|
+
3. Run your apps using Bundler.
|
275
|
+
4. Sass users: Install the latest version of Listen and try again.
|
276
|
+
|
277
|
+
#### Simplified Bundler and Sass example
|
278
|
+
Create a Gemfile with these lines:
|
279
|
+
```
|
280
|
+
source 'https://rubygems.org'
|
281
|
+
gem 'listen'
|
282
|
+
gem 'sass'
|
283
|
+
```
|
284
|
+
Next, use Bundler to update gems:
|
285
|
+
```
|
286
|
+
$ bundle update
|
287
|
+
$ bundle exec sass --watch # ... or whatever app is using Listen.
|
288
|
+
```
|
289
|
+
|
290
|
+
### Increasing the amount of inotify watchers
|
291
|
+
|
292
|
+
If you are running Debian, RedHat, or another similar Linux distribution, run the following in a terminal:
|
293
|
+
```
|
294
|
+
$ sudo sh -c "echo fs.inotify.max_user_watches=524288 >> /etc/sysctl.conf"
|
295
|
+
$ sudo sysctl -p
|
296
|
+
```
|
297
|
+
If you are running ArchLinux, search the `/etc/sysctl.d/` directory for config files with the setting:
|
298
|
+
```
|
299
|
+
$ grep -H -s "fs.inotify.max_user_watches" /etc/sysctl.d/*
|
300
|
+
/etc/sysctl.d/40-max_user_watches.conf:fs.inotify.max_user_watches=100000
|
301
|
+
```
|
302
|
+
Then change the setting in the file you found above to a higher value (see [here](https://www.archlinux.org/news/deprecation-of-etcsysctlconf/) for why):
|
303
|
+
```
|
304
|
+
$ sudo sh -c "echo fs.inotify.max_user_watches=524288 > /etc/sysctl.d/40-max-user-watches.conf"
|
305
|
+
$ sudo sysctl --system
|
306
|
+
```
|
307
|
+
|
308
|
+
#### The technical details
|
309
|
+
Listen uses `inotify` by default on Linux to monitor directories for changes.
|
310
|
+
It's not uncommon to encounter a system limit on the number of files you can monitor.
|
311
|
+
For example, Ubuntu Lucid's (64bit) `inotify` limit is set to 8192.
|
312
|
+
|
313
|
+
You can get your current inotify file watch limit by executing:
|
314
|
+
```
|
315
|
+
$ cat /proc/sys/fs/inotify/max_user_watches
|
316
|
+
```
|
317
|
+
When this limit is not enough to monitor all files inside a directory, the limit must be increased for Listen to work properly.
|
318
|
+
|
319
|
+
You can set a new limit temporarily with:
|
320
|
+
```
|
321
|
+
$ sudo sysctl fs.inotify.max_user_watches=524288
|
322
|
+
$ sudo sysctl -p
|
323
|
+
```
|
324
|
+
If you like to make your limit permanent, use:
|
325
|
+
```
|
326
|
+
$ sudo sh -c "echo fs.inotify.max_user_watches=524288 >> /etc/sysctl.conf"
|
327
|
+
$ sudo sysctl -p
|
328
|
+
```
|
329
|
+
You may also need to pay attention to the values of `max_queued_events` and `max_user_instances` if Listen keeps on complaining.
|
330
|
+
|
331
|
+
#### More info
|
332
|
+
Man page for [inotify(7)](https://linux.die.net/man/7/inotify).
|
333
|
+
Blog post: [limit of inotify](https://blog.sorah.jp/2012/01/24/inotify-limitation).
|
263
334
|
|
264
335
|
### Issues and Troubleshooting
|
265
336
|
|
@@ -267,7 +338,35 @@ If the gem doesn't work as expected, start by setting `LISTEN_GEM_DEBUGGING=debu
|
|
267
338
|
|
268
339
|
*NOTE: without providing the output after setting the `LISTEN_GEM_DEBUGGING=debug` environment variable, it is usually impossible to guess why `listen` is not working as expected.*
|
269
340
|
|
270
|
-
|
341
|
+
#### 3 steps before you start diagnosing problems
|
342
|
+
These 3 steps will:
|
343
|
+
|
344
|
+
- help quickly troubleshoot obscure problems (trust me, most of them are obscure)
|
345
|
+
- help quickly identify the area of the problem (a full list is below)
|
346
|
+
- help you get familiar with listen's diagnostic mode (it really comes in handy, trust me)
|
347
|
+
- help you create relevant output before you submit an issue (so we can respond with answers instead of tons of questions)
|
348
|
+
|
349
|
+
Step 1 - The most important option in Listen
|
350
|
+
For effective troubleshooting set the `LISTEN_GEM_DEBUGGING=info` variable before starting `listen`.
|
351
|
+
|
352
|
+
Step 2 - Verify polling works
|
353
|
+
Polling has to work ... or something is really wrong (and we need to know that before anything else).
|
354
|
+
|
355
|
+
(see force_polling option).
|
356
|
+
|
357
|
+
After starting `listen`, you should see something like:
|
358
|
+
```
|
359
|
+
INFO -- : Record.build(): 0.06773114204406738 seconds
|
360
|
+
```
|
361
|
+
Step 3 - Trigger some changes directly without using editors or apps
|
362
|
+
Make changes e.g. touch foo or echo "a" >> foo (for troubleshooting, avoid using an editor which could generate too many misleading events).
|
363
|
+
|
364
|
+
You should see something like:
|
365
|
+
```
|
366
|
+
INFO -- : listen: raw changes: [[:added, "/home/me/foo"]]
|
367
|
+
INFO -- : listen: final changes: {:modified=>[], :added=>["/home/me/foo"], :removed=>[]}
|
368
|
+
```
|
369
|
+
"raw changes" contains changes collected during the :wait_for_delay and :latency intervals, while "final changes" is what listen decided are relevant changes (for better editor support).
|
271
370
|
|
272
371
|
## Performance
|
273
372
|
|
@@ -289,7 +388,11 @@ Also, if the directories you're watching contain many files, make sure you're:
|
|
289
388
|
|
290
389
|
When in doubt, `LISTEN_GEM_DEBUGGING=debug` can help discover the actual events and time they happened.
|
291
390
|
|
292
|
-
|
391
|
+
## Tips and Techniques
|
392
|
+
- Watch only directories you're interested in.
|
393
|
+
- Set your editor to save quickly (e.g. without backup files, without atomic-save)
|
394
|
+
- Tweak the `:latency` and `:wait_for_delay` options until you get good results (see [options](#options)).
|
395
|
+
- Add `:ignore` rules to silence all events you don't care about (reduces a lot of noise, especially if you use it on directories)
|
293
396
|
|
294
397
|
## Development
|
295
398
|
|
data/lib/listen/adapter/base.rb
CHANGED
@@ -51,7 +51,7 @@ module Listen
|
|
51
51
|
# TODO: separate config per directory (some day maybe)
|
52
52
|
change_config = Change::Config.new(config.queue, config.silencer)
|
53
53
|
config.directories.each do |dir|
|
54
|
-
record = Record.new(dir)
|
54
|
+
record = Record.new(dir, config.silencer)
|
55
55
|
snapshot = Change.new(change_config, record)
|
56
56
|
@snapshots[dir] = snapshot
|
57
57
|
end
|
@@ -93,9 +93,9 @@ module Listen
|
|
93
93
|
end
|
94
94
|
|
95
95
|
def _timed(title)
|
96
|
-
start =
|
96
|
+
start = MonotonicTime.now
|
97
97
|
yield
|
98
|
-
diff =
|
98
|
+
diff = MonotonicTime.now - start
|
99
99
|
Listen.logger.info format('%s: %.05f seconds', title, diff)
|
100
100
|
rescue
|
101
101
|
Listen.logger.warn "#{title} crashed: #{$ERROR_INFO.inspect}"
|
data/lib/listen/adapter/linux.rb
CHANGED
@@ -21,20 +21,17 @@ module Listen
|
|
21
21
|
|
22
22
|
private
|
23
23
|
|
24
|
-
|
25
|
-
'/
|
26
|
-
|
27
|
-
INOTIFY_LIMIT_MESSAGE = <<-EOS.gsub(/^\s*/, '')
|
28
|
-
FATAL: Listen error: unable to monitor directories for changes.
|
29
|
-
Visit #{WIKI_URL} for info on how to fix this.
|
30
|
-
EOS
|
24
|
+
README_URL = 'https://github.com/guard/listen'\
|
25
|
+
'/blob/master/README.md#increasing-the-amount-of-inotify-watchers'
|
31
26
|
|
32
27
|
def _configure(directory, &callback)
|
33
28
|
require 'rb-inotify'
|
34
29
|
@worker ||= ::INotify::Notifier.new
|
35
30
|
@worker.watch(directory.to_s, *options.events, &callback)
|
36
31
|
rescue Errno::ENOSPC
|
37
|
-
|
32
|
+
raise ::Listen::Error::INotifyMaxWatchesExceeded, <<~EOS
|
33
|
+
Unable to monitor directories for changes because iNotify max watches exceeded. See #{README_URL} .
|
34
|
+
EOS
|
38
35
|
end
|
39
36
|
|
40
37
|
def _run
|
@@ -21,12 +21,13 @@ module Listen
|
|
21
21
|
|
22
22
|
def _run
|
23
23
|
loop do
|
24
|
-
start =
|
24
|
+
start = MonotonicTime.now
|
25
25
|
@polling_callbacks.each do |callback|
|
26
26
|
callback.call(nil)
|
27
|
-
nap_time = options.latency - (
|
28
|
-
|
29
|
-
|
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
|
30
31
|
end
|
31
32
|
end
|
32
33
|
end
|
data/lib/listen/directory.rb
CHANGED
@@ -36,7 +36,7 @@ module Listen
|
|
36
36
|
end
|
37
37
|
|
38
38
|
# TODO: this is not tested properly
|
39
|
-
previous = previous.reject { |entry, _| current.include?
|
39
|
+
previous = previous.reject { |entry, _| current.include?(path + entry) }
|
40
40
|
|
41
41
|
_async_changes(snapshot, Pathname.new(rel_path), previous, options)
|
42
42
|
rescue Errno::ENOENT, Errno::EHOSTDOWN
|
data/lib/listen/error.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Besides programming error exceptions like ArgumentError,
|
4
|
+
# all public interface exceptions should be declared here and inherit from Listen::Error.
|
5
|
+
module Listen
|
6
|
+
class Error < RuntimeError
|
7
|
+
class NotStarted < Error; end
|
8
|
+
class SymlinkLoop < Error; end
|
9
|
+
class INotifyMaxWatchesExceeded < Error; end
|
10
|
+
end
|
11
|
+
end
|
data/lib/listen/event/config.rb
CHANGED
data/lib/listen/event/loop.rb
CHANGED
@@ -5,15 +5,15 @@ require 'thread'
|
|
5
5
|
require 'timeout'
|
6
6
|
require 'listen/event/processor'
|
7
7
|
require 'listen/thread'
|
8
|
+
require 'listen/error'
|
8
9
|
|
9
10
|
module Listen
|
10
11
|
module Event
|
11
12
|
class Loop
|
12
13
|
include Listen::FSM
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
end
|
15
|
+
Error = ::Listen::Error
|
16
|
+
NotStarted = ::Listen::Error::NotStarted # for backward compatibility
|
17
17
|
|
18
18
|
start_state :pre_start
|
19
19
|
state :pre_start
|
@@ -40,6 +40,7 @@ module Listen
|
|
40
40
|
|
41
41
|
MAX_STARTUP_SECONDS = 5.0
|
42
42
|
|
43
|
+
# @raises Error::NotStarted if background thread hasn't started in MAX_STARTUP_SECONDS
|
43
44
|
def start
|
44
45
|
# TODO: use a Fiber instead?
|
45
46
|
return unless state == :pre_start
|
@@ -52,7 +53,7 @@ module Listen
|
|
52
53
|
|
53
54
|
Listen.logger.debug("Waiting for processing to start...")
|
54
55
|
|
55
|
-
wait_for_state(:started, MAX_STARTUP_SECONDS) or
|
56
|
+
wait_for_state(:started, timeout: MAX_STARTUP_SECONDS) or
|
56
57
|
raise Error::NotStarted, "thread didn't start in #{MAX_STARTUP_SECONDS} seconds (in state: #{state.inspect})"
|
57
58
|
|
58
59
|
Listen.logger.debug('Processing started.')
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'listen/monotonic_time'
|
4
|
+
|
3
5
|
module Listen
|
4
6
|
module Event
|
5
7
|
class Processor
|
@@ -33,7 +35,7 @@ module Listen
|
|
33
35
|
|
34
36
|
def _wait_until_events_calm_down
|
35
37
|
loop do
|
36
|
-
now =
|
38
|
+
now = MonotonicTime.now
|
37
39
|
|
38
40
|
# Assure there's at least latency between callbacks to allow
|
39
41
|
# for accumulating changes
|
@@ -70,7 +72,7 @@ module Listen
|
|
70
72
|
end
|
71
73
|
|
72
74
|
def _remember_time_of_first_unprocessed_event
|
73
|
-
@_remember_time_of_first_unprocessed_event ||=
|
75
|
+
@_remember_time_of_first_unprocessed_event ||= MonotonicTime.now
|
74
76
|
end
|
75
77
|
|
76
78
|
def _reset_no_unprocessed_events
|
@@ -85,7 +87,7 @@ module Listen
|
|
85
87
|
# returns the event or `nil` when the event_queue is closed
|
86
88
|
def _wait_until_events
|
87
89
|
config.event_queue.pop.tap do |_event|
|
88
|
-
@_remember_time_of_first_unprocessed_event ||=
|
90
|
+
@_remember_time_of_first_unprocessed_event ||= MonotonicTime.now
|
89
91
|
end
|
90
92
|
end
|
91
93
|
|
@@ -96,10 +98,6 @@ module Listen
|
|
96
98
|
end
|
97
99
|
end
|
98
100
|
|
99
|
-
def _timestamp
|
100
|
-
config.timestamp
|
101
|
-
end
|
102
|
-
|
103
101
|
# for easier testing without sleep loop
|
104
102
|
def _process_changes(event)
|
105
103
|
_reset_no_unprocessed_events
|
@@ -113,13 +111,13 @@ module Listen
|
|
113
111
|
result = [hash[:modified], hash[:added], hash[:removed]]
|
114
112
|
return if result.all?(&:empty?)
|
115
113
|
|
116
|
-
block_start =
|
114
|
+
block_start = MonotonicTime.now
|
117
115
|
exception_note = " (exception)"
|
118
116
|
::Listen::Thread.rescue_and_log('_process_changes') do
|
119
117
|
config.call(*result)
|
120
118
|
exception_note = nil
|
121
119
|
end
|
122
|
-
Listen.logger.debug "Callback#{exception_note} took #{
|
120
|
+
Listen.logger.debug "Callback#{exception_note} took #{MonotonicTime.now - block_start} sec"
|
123
121
|
end
|
124
122
|
|
125
123
|
attr_reader :config
|
data/lib/listen/file.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'digest
|
3
|
+
require 'digest'
|
4
4
|
|
5
5
|
module Listen
|
6
6
|
class File
|
@@ -53,7 +53,7 @@ module Listen
|
|
53
53
|
# then at ???14.998, but the fstat time would be ???14.0 in
|
54
54
|
# both cases).
|
55
55
|
#
|
56
|
-
# If change
|
56
|
+
# If change happened at ???14.999997, the mtime is 14.0, so for
|
57
57
|
# an mtime=???14.0 we assume it could even be almost ???15.0
|
58
58
|
#
|
59
59
|
# So if Time.now.to_f is ???15.999998 and stat reports mtime
|
@@ -67,9 +67,11 @@ module Listen
|
|
67
67
|
#
|
68
68
|
return if data[:mtime].to_i + 2 <= Time.now.to_f
|
69
69
|
|
70
|
-
|
71
|
-
record.update_file(rel_path, data.merge(
|
72
|
-
|
70
|
+
sha = Digest::SHA256.file(path).digest
|
71
|
+
record.update_file(rel_path, data.merge(sha: sha))
|
72
|
+
if record_data[:sha] && sha != record_data[:sha]
|
73
|
+
:modified
|
74
|
+
end
|
73
75
|
rescue SystemCallError
|
74
76
|
record.unset_path(rel_path)
|
75
77
|
:removed
|
data/lib/listen/fsm.rb
CHANGED
@@ -53,6 +53,9 @@ module Listen
|
|
53
53
|
# if not already, waits for a state change (up to timeout seconds--`nil` means infinite)
|
54
54
|
# returns truthy iff the transition to one of the desired state has occurred
|
55
55
|
def wait_for_state(*wait_for_states, timeout: nil)
|
56
|
+
wait_for_states.each do |state|
|
57
|
+
state.is_a?(Symbol) or raise ArgumentError, "states must be symbols (got #{state.inspect})"
|
58
|
+
end
|
56
59
|
@mutex.synchronize do
|
57
60
|
if !wait_for_states.include?(@state)
|
58
61
|
@state_changed.wait(@mutex, timeout)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Listen
|
4
|
+
module MonotonicTime
|
5
|
+
class << self
|
6
|
+
if defined?(Process::CLOCK_MONOTONIC)
|
7
|
+
|
8
|
+
def now
|
9
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
10
|
+
end
|
11
|
+
|
12
|
+
elsif defined?(Process::CLOCK_MONOTONIC_RAW)
|
13
|
+
|
14
|
+
def now
|
15
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC_RAW)
|
16
|
+
end
|
17
|
+
|
18
|
+
else
|
19
|
+
|
20
|
+
def now
|
21
|
+
Time.now.to_f
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'set'
|
4
|
+
require 'listen/error'
|
4
5
|
|
5
6
|
module Listen
|
6
7
|
# @private api
|
7
8
|
class Record
|
8
9
|
class SymlinkDetector
|
9
|
-
|
10
|
+
README_URL = 'https://github.com/guard/listen/blob/master/README.md'
|
10
11
|
|
11
12
|
SYMLINK_LOOP_ERROR = <<-EOS
|
12
13
|
** ERROR: directory is already being watched! **
|
@@ -15,11 +16,10 @@ module Listen
|
|
15
16
|
|
16
17
|
is already being watched through: %s
|
17
18
|
|
18
|
-
MORE INFO: #{
|
19
|
+
MORE INFO: #{README_URL}
|
19
20
|
EOS
|
20
21
|
|
21
|
-
|
22
|
-
end
|
22
|
+
Error = ::Listen::Error # for backward compatibility
|
23
23
|
|
24
24
|
def initialize
|
25
25
|
@real_dirs = Set.new
|
@@ -27,14 +27,14 @@ module Listen
|
|
27
27
|
|
28
28
|
def verify_unwatched!(entry)
|
29
29
|
real_path = entry.real_path
|
30
|
-
@real_dirs.add?(real_path)
|
30
|
+
@real_dirs.add?(real_path) or _fail(entry.sys_path, real_path)
|
31
31
|
end
|
32
32
|
|
33
33
|
private
|
34
34
|
|
35
35
|
def _fail(symlinked, real_path)
|
36
36
|
warn(format(SYMLINK_LOOP_ERROR, symlinked, real_path))
|
37
|
-
raise Error, 'Failed due to looped symlinks'
|
37
|
+
raise ::Listen::Error::SymlinkLoop, 'Failed due to looped symlinks'
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
data/lib/listen/record.rb
CHANGED
@@ -11,14 +11,15 @@ module Listen
|
|
11
11
|
|
12
12
|
attr_reader :root
|
13
13
|
|
14
|
-
def initialize(directory)
|
15
|
-
|
14
|
+
def initialize(directory, silencer)
|
15
|
+
reset_tree
|
16
16
|
@root = directory.to_s
|
17
|
+
@silencer = silencer
|
17
18
|
end
|
18
19
|
|
19
20
|
def add_dir(rel_path)
|
20
|
-
if !
|
21
|
-
@tree[rel_path]
|
21
|
+
if !empty_dirname?(rel_path.to_s)
|
22
|
+
@tree[rel_path.to_s]
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
@@ -34,44 +35,32 @@ module Listen
|
|
34
35
|
|
35
36
|
def file_data(rel_path)
|
36
37
|
dirname, basename = Pathname(rel_path).split.map(&:to_s)
|
37
|
-
if
|
38
|
-
@tree[basename] ||= {}
|
38
|
+
if empty_dirname?(dirname)
|
39
39
|
@tree[basename].dup
|
40
40
|
else
|
41
|
-
@tree[dirname] ||= {}
|
42
41
|
@tree[dirname][basename] ||= {}
|
43
42
|
@tree[dirname][basename].dup
|
44
43
|
end
|
45
44
|
end
|
46
45
|
|
47
46
|
def dir_entries(rel_path)
|
48
|
-
|
47
|
+
rel_path_s = rel_path.to_s
|
48
|
+
subtree = if empty_dirname?(rel_path_s)
|
49
49
|
@tree
|
50
50
|
else
|
51
|
-
|
51
|
+
@tree[rel_path_s]
|
52
52
|
end
|
53
53
|
|
54
|
-
subtree.
|
55
|
-
# only
|
56
|
-
values.
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
def _sub_tree(rel_path)
|
61
|
-
@tree.each_with_object({}) do |(path, meta), result|
|
62
|
-
next unless path.start_with?(rel_path)
|
63
|
-
|
64
|
-
if path == rel_path
|
65
|
-
result.merge!(meta)
|
66
|
-
else
|
67
|
-
sub_path = path.sub(%r{\A#{rel_path}/?}, '')
|
68
|
-
result[sub_path] = meta
|
54
|
+
subtree.each_with_object({}) do |(key, values), result|
|
55
|
+
# only return data for file entries inside the dir (which will each be sub-hashes)
|
56
|
+
if values.is_a?(Hash)
|
57
|
+
result[key] = values.has_key?(:mtime) ? values : {}
|
69
58
|
end
|
70
59
|
end
|
71
60
|
end
|
72
61
|
|
73
62
|
def build
|
74
|
-
|
63
|
+
reset_tree
|
75
64
|
# TODO: test with a file name given
|
76
65
|
# TODO: test other permissions
|
77
66
|
# TODO: test with mixed encoding
|
@@ -83,15 +72,18 @@ module Listen
|
|
83
72
|
|
84
73
|
private
|
85
74
|
|
86
|
-
def
|
87
|
-
|
75
|
+
def empty_dirname?(dirname)
|
76
|
+
dirname == '.' || dirname == ''
|
77
|
+
end
|
78
|
+
|
79
|
+
def reset_tree
|
80
|
+
@tree = Hash.new { |h, k| h[k] = {} }
|
88
81
|
end
|
89
82
|
|
90
83
|
def _fast_update_file(dirname, basename, data)
|
91
|
-
if
|
92
|
-
@tree[basename] =
|
84
|
+
if empty_dirname?(dirname.to_s)
|
85
|
+
@tree[basename] = @tree[basename].merge(data)
|
93
86
|
else
|
94
|
-
@tree[dirname] ||= {}
|
95
87
|
@tree[dirname][basename] = (@tree[dirname][basename] || {}).merge(data)
|
96
88
|
end
|
97
89
|
end
|
@@ -99,7 +91,7 @@ module Listen
|
|
99
91
|
def _fast_unset_path(dirname, basename)
|
100
92
|
# this may need to be reworked to properly remove
|
101
93
|
# entries from a tree, without adding non-existing dirs to the record
|
102
|
-
if
|
94
|
+
if empty_dirname?(dirname.to_s)
|
103
95
|
if @tree.key?(basename)
|
104
96
|
@tree.delete(basename)
|
105
97
|
end
|
@@ -110,6 +102,8 @@ module Listen
|
|
110
102
|
|
111
103
|
def _fast_build_dir(remaining, symlink_detector)
|
112
104
|
entry = remaining.pop
|
105
|
+
return if @silencer.silenced?(entry.record_dir_key, :dir)
|
106
|
+
|
113
107
|
children = entry.children # NOTE: children() implicitly tests if dir
|
114
108
|
symlink_detector.verify_unwatched!(entry)
|
115
109
|
children.each { |child| remaining << child }
|
data/lib/listen/silencer.rb
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
module Listen
|
4
4
|
class Silencer
|
5
5
|
# The default list of directories that get ignored.
|
6
|
-
|
7
|
-
|
6
|
+
DEFAULT_IGNORED_FILES = %r{\A(?:
|
7
|
+
\.git
|
8
8
|
| \.svn
|
9
9
|
| \.hg
|
10
10
|
| \.rbx
|
@@ -13,8 +13,12 @@ module Listen
|
|
13
13
|
| vendor/bundle
|
14
14
|
| log
|
15
15
|
| tmp
|
16
|
-
|vendor/ruby
|
17
|
-
|
16
|
+
| vendor/ruby
|
17
|
+
|
18
|
+
# emacs temp files
|
19
|
+
| \#.+\#
|
20
|
+
| \.\#.+
|
21
|
+
)(/|\z)}x.freeze
|
18
22
|
|
19
23
|
# The default list of files that get ignored.
|
20
24
|
DEFAULT_IGNORED_EXTENSIONS = %r{(?:
|
@@ -35,7 +39,7 @@ module Listen
|
|
35
39
|
| ^4913
|
36
40
|
|
37
41
|
# Sed temporary files - but without actual words, like 'sedatives'
|
38
|
-
| (
|
42
|
+
| (?:\A
|
39
43
|
sed
|
40
44
|
|
41
45
|
(?:
|
@@ -55,25 +59,23 @@ module Listen
|
|
55
59
|
| \.DS_Store
|
56
60
|
| \.tmp
|
57
61
|
| ~
|
58
|
-
)
|
62
|
+
)\z}x.freeze
|
59
63
|
|
64
|
+
# TODO: deprecate these mutators; use attr_reader instead
|
60
65
|
attr_accessor :only_patterns, :ignore_patterns
|
61
66
|
|
62
|
-
def initialize
|
63
|
-
configure(
|
67
|
+
def initialize(**options)
|
68
|
+
configure(options)
|
64
69
|
end
|
65
70
|
|
71
|
+
# TODO: deprecate this mutator
|
66
72
|
def configure(options)
|
67
73
|
@only_patterns = options[:only] ? Array(options[:only]) : nil
|
68
74
|
@ignore_patterns = _init_ignores(options[:ignore], options[:ignore!])
|
69
75
|
end
|
70
76
|
|
71
|
-
# Note: relative_path is temporarily expected to be a relative Pathname to
|
72
|
-
# make refactoring easier (ideally, it would take a string)
|
73
|
-
|
74
|
-
# TODO: switch type and path places - and verify
|
75
77
|
def silenced?(relative_path, type)
|
76
|
-
path = relative_path.to_s
|
78
|
+
path = relative_path.to_s # in case it is a Pathname
|
77
79
|
|
78
80
|
_ignore?(path) || (only_patterns && type == :file && !_only?(path))
|
79
81
|
end
|
@@ -91,7 +93,7 @@ module Listen
|
|
91
93
|
def _init_ignores(ignores, overrides)
|
92
94
|
patterns = []
|
93
95
|
unless overrides
|
94
|
-
patterns <<
|
96
|
+
patterns << DEFAULT_IGNORED_FILES
|
95
97
|
patterns << DEFAULT_IGNORED_EXTENSIONS
|
96
98
|
end
|
97
99
|
|
data/lib/listen/thread.rb
CHANGED
@@ -24,7 +24,7 @@ module Listen
|
|
24
24
|
|
25
25
|
def rescue_and_log(method_name, *args, caller_stack: nil)
|
26
26
|
yield(*args)
|
27
|
-
rescue
|
27
|
+
rescue => exception
|
28
28
|
_log_exception(exception, method_name, caller_stack: caller_stack)
|
29
29
|
end
|
30
30
|
|
data/lib/listen/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: listen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thibaud Guillaume-Gentil
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rb-fsevent
|
@@ -76,6 +76,7 @@ files:
|
|
76
76
|
- lib/listen/change.rb
|
77
77
|
- lib/listen/cli.rb
|
78
78
|
- lib/listen/directory.rb
|
79
|
+
- lib/listen/error.rb
|
79
80
|
- lib/listen/event/config.rb
|
80
81
|
- lib/listen/event/loop.rb
|
81
82
|
- lib/listen/event/processor.rb
|
@@ -85,6 +86,7 @@ files:
|
|
85
86
|
- lib/listen/listener.rb
|
86
87
|
- lib/listen/listener/config.rb
|
87
88
|
- lib/listen/logger.rb
|
89
|
+
- lib/listen/monotonic_time.rb
|
88
90
|
- lib/listen/options.rb
|
89
91
|
- lib/listen/queue_optimizer.rb
|
90
92
|
- lib/listen/record.rb
|
@@ -101,10 +103,9 @@ metadata:
|
|
101
103
|
allowed_push_host: https://rubygems.org
|
102
104
|
bug_tracker_uri: https://github.com/guard/listen/issues
|
103
105
|
changelog_uri: https://github.com/guard/listen/releases
|
104
|
-
documentation_uri: https://www.rubydoc.info/gems/listen/3.
|
106
|
+
documentation_uri: https://www.rubydoc.info/gems/listen/3.7.1
|
105
107
|
homepage_uri: https://github.com/guard/listen
|
106
|
-
source_code_uri: https://github.com/guard/listen/tree/v3.
|
107
|
-
wiki_uri: https://github.com/guard/listen/wiki
|
108
|
+
source_code_uri: https://github.com/guard/listen/tree/v3.7.1
|
108
109
|
post_install_message:
|
109
110
|
rdoc_options: []
|
110
111
|
require_paths:
|
@@ -113,7 +114,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
113
114
|
requirements:
|
114
115
|
- - ">="
|
115
116
|
- !ruby/object:Gem::Version
|
116
|
-
version: 2.
|
117
|
+
version: 2.4.0
|
117
118
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
119
|
requirements:
|
119
120
|
- - ">="
|