listen 3.3.3 → 3.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  - - ">="