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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e8a5f611b09b7689da35117369be07e2cb74ed77575dc7d894c12629767ad82
4
- data.tar.gz: c7995d9785eb3202fa13528489a0c48e54c70fc201c750cb885777d36151b316
3
+ metadata.gz: 800a9d0af997cb48bc330b0aeaecdcb39b53e9bdeb2dbed6c08fd03b237a3d36
4
+ data.tar.gz: 5caa12c52d09e7447d0f1b43fddadf03e4e33ae4c611bd0ed7d367e3cd3a280b
5
5
  SHA512:
6
- metadata.gz: fb0d331747afe760ab96a3eecf20d041e1e9fec0a2c49b678289fe56250c2a357a4826c9d4c9da38c9db7f819e7d61f6b9f5245c23a34c4eb9f77868abde2ec0
7
- data.tar.gz: 3c7d8b1f91224ea4a10b4f039e334fbb85c0eae14996bf2df9c32a9487325f94a5e1d1ce87abefa23dce2f50b0bac2069950d05ec1f561552fb9c8493c31fd83
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/wiki/Troubleshooting) for known issues, solutions and workarounds.
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/wiki/Troubleshooting) for details.
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)- see [Duplicate directory errors](https://github.com/guard/listen/wiki/Duplicate-directory-errors)).
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', '~> 3.3' # NOTE: for TCP functionality, use '~> 2.10' for now
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 / unpause / stop
100
+ ### Pause / start / stop
105
101
 
106
- Listeners can also be easily paused/unpaused:
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? # => false
108
+ listener.paused? # => false
113
109
  listener.processing? # => true
114
110
 
115
- listener.pause # stops processing changes (but keeps on collecting them)
116
- listener.paused? # => true
111
+ listener.pause # stops processing changes (but keeps on collecting them)
112
+ listener.paused? # => true
117
113
  listener.processing? # => false
118
114
 
119
- listener.unpause # resumes processing changes ("start" would do the same)
120
- listener.stop # stop both listening to changes and processing them
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 DEFAULT_IGNORED_DIRECTORIES and DEFAULT_IGNORED_EXTENSIONS in Listen::Silencer).
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 DEFAULT_IGNORED_DIRECTORIES and DEFAULT_IGNORED_EXTENSIONS in Listen::Silencer
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' if Gem.win_platform?
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
- Please visit the [installation section of the Listen WIKI](https://github.com/guard/listen/wiki#installation) for more information and options for potential fixes.
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
- See [TROUBLESHOOTING](https://github.com/guard/listen/wiki/Troubleshooting)
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
- See also [Tips and Techniques](https://github.com/guard/listen/wiki/Tips-and-Techniques).
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
 
@@ -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 = Time.now.to_f
96
+ start = MonotonicTime.now
97
97
  yield
98
- diff = Time.now.to_f - start
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}"
@@ -21,20 +21,17 @@ module Listen
21
21
 
22
22
  private
23
23
 
24
- WIKI_URL = 'https://github.com/guard/listen'\
25
- '/wiki/Increasing-the-amount-of-inotify-watchers'
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
- abort(INOTIFY_LIMIT_MESSAGE)
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 = Time.now.to_f
24
+ start = MonotonicTime.now
25
25
  @polling_callbacks.each do |callback|
26
26
  callback.call(nil)
27
- nap_time = options.latency - (Time.now.to_f - start)
28
- # TODO: warn if nap_time is negative (polling too slow)
29
- sleep(nap_time) if nap_time > 0
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
@@ -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? path + entry }
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
@@ -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
@@ -28,10 +28,6 @@ module Listen
28
28
  @block&.call(*args)
29
29
  end
30
30
 
31
- def timestamp
32
- Time.now.to_f
33
- end
34
-
35
31
  def callable?
36
32
  @block
37
33
  end
@@ -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
- class Error < RuntimeError
15
- class NotStarted < Error; end
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 = _timestamp
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 ||= _timestamp
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 ||= _timestamp
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 = _timestamp
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 #{_timestamp - block_start} sec"
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/md5'
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 happend at ???14.999997, the mtime is 14.0, so for
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
- md5 = Digest::MD5.file(path).digest
71
- record.update_file(rel_path, data.merge(md5: md5))
72
- :modified if record_data[:md5] && md5 != record_data[:md5]
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
- WIKI = 'https://github.com/guard/listen/wiki/Duplicate-directory-errors'
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: #{WIKI}
19
+ MORE INFO: #{README_URL}
19
20
  EOS
20
21
 
21
- class Error < RuntimeError
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) || _fail(entry.sys_path, 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
- @tree = _auto_hash
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 ![nil, '', '.'].include?(rel_path)
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 [nil, '', '.'].include? dirname
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
- subtree = if [nil, '', '.'].include? rel_path.to_s
47
+ rel_path_s = rel_path.to_s
48
+ subtree = if empty_dirname?(rel_path_s)
49
49
  @tree
50
50
  else
51
- _sub_tree(rel_path)
51
+ @tree[rel_path_s]
52
52
  end
53
53
 
54
- subtree.transform_values do |values|
55
- # only get data for file entries
56
- values.key?(:mtime) ? values : {}
57
- end
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
- @tree = _auto_hash
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 _auto_hash
87
- Hash.new { |h, k| h[k] = {} }
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 [nil, '', '.'].include?(dirname)
92
- @tree[basename] = (@tree[basename] || {}).merge(data)
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 [nil, '', '.'].include?(dirname)
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 }
@@ -3,8 +3,8 @@
3
3
  module Listen
4
4
  class Silencer
5
5
  # The default list of directories that get ignored.
6
- DEFAULT_IGNORED_DIRECTORIES = %r{^(?:
7
- \.git
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
- )(/|$)}x.freeze
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
- )$}x.freeze
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 << DEFAULT_IGNORED_DIRECTORIES
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 Exception => exception # rubocop:disable Lint/RescueException
27
+ rescue => exception
28
28
  _log_exception(exception, method_name, caller_stack: caller_stack)
29
29
  end
30
30
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Listen
4
- VERSION = '3.3.3'
4
+ VERSION = '3.7.1'
5
5
  end
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.3.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: 2020-11-30 00:00:00.000000000 Z
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.3.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.3.3
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.2.7
117
+ version: 2.4.0
117
118
  required_rubygems_version: !ruby/object:Gem::Requirement
118
119
  requirements:
119
120
  - - ">="