listen 2.7.4 → 2.7.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +232 -0
  3. data/.travis.yml +6 -3
  4. data/Gemfile +9 -1
  5. data/Guardfile +6 -1
  6. data/README.md +17 -4
  7. data/lib/listen.rb +9 -4
  8. data/lib/listen/adapter.rb +5 -7
  9. data/lib/listen/adapter/base.rb +8 -5
  10. data/lib/listen/adapter/bsd.rb +58 -21
  11. data/lib/listen/adapter/darwin.rb +2 -4
  12. data/lib/listen/adapter/linux.rb +20 -10
  13. data/lib/listen/adapter/polling.rb +0 -2
  14. data/lib/listen/adapter/tcp.rb +6 -5
  15. data/lib/listen/adapter/windows.rb +8 -6
  16. data/lib/listen/change.rb +1 -2
  17. data/lib/listen/cli.rb +25 -22
  18. data/lib/listen/directory.rb +8 -6
  19. data/lib/listen/listener.rb +25 -19
  20. data/lib/listen/record.rb +4 -2
  21. data/lib/listen/silencer.rb +55 -25
  22. data/lib/listen/tcp.rb +9 -0
  23. data/lib/listen/tcp/broadcaster.rb +0 -2
  24. data/lib/listen/tcp/listener.rb +13 -8
  25. data/lib/listen/tcp/message.rb +0 -2
  26. data/lib/listen/version.rb +1 -1
  27. data/listen.gemspec +4 -3
  28. data/spec/acceptance/listen_spec.rb +190 -109
  29. data/spec/acceptance/tcp_spec.rb +28 -26
  30. data/spec/lib/listen/adapter/base_spec.rb +14 -12
  31. data/spec/lib/listen/adapter/bsd_spec.rb +5 -2
  32. data/spec/lib/listen/adapter/darwin_spec.rb +5 -2
  33. data/spec/lib/listen/adapter/linux_spec.rb +40 -25
  34. data/spec/lib/listen/adapter/polling_spec.rb +29 -14
  35. data/spec/lib/listen/adapter/tcp_spec.rb +24 -6
  36. data/spec/lib/listen/adapter/windows_spec.rb +5 -2
  37. data/spec/lib/listen/adapter_spec.rb +20 -17
  38. data/spec/lib/listen/change_spec.rb +36 -26
  39. data/spec/lib/listen/directory_spec.rb +128 -71
  40. data/spec/lib/listen/file_spec.rb +67 -34
  41. data/spec/lib/listen/listener_spec.rb +135 -105
  42. data/spec/lib/listen/record_spec.rb +32 -29
  43. data/spec/lib/listen/silencer_spec.rb +78 -56
  44. data/spec/lib/listen/tcp/broadcaster_spec.rb +3 -2
  45. data/spec/lib/listen/tcp/listener_spec.rb +17 -11
  46. data/spec/lib/listen/tcp/message_spec.rb +1 -1
  47. data/spec/lib/listen_spec.rb +18 -6
  48. data/spec/spec_helper.rb +5 -1
  49. data/spec/support/acceptance_helper.rb +3 -3
  50. data/spec/support/fixtures_helper.rb +10 -9
  51. metadata +17 -15
@@ -1,26 +1,46 @@
1
+ # Listener implementation for BSD's `kqueue`.
2
+ # @see http://www.freebsd.org/cgi/man.cgi?query=kqueue
3
+ # @see https://github.com/mat813/rb-kqueue/blob/master/lib/rb-kqueue/queue.rb
4
+ #
1
5
  module Listen
2
6
  module Adapter
3
-
4
- # Listener implementation for BSD's `kqueue`.
5
- #
6
7
  class BSD < Base
7
8
  # Watched kqueue events
8
9
  #
9
- # @see http://www.freebsd.org/cgi/man.cgi?query=kqueue
10
- # @see https://github.com/mat813/rb-kqueue/blob/master/lib/rb-kqueue/queue.rb
11
- #
12
10
  EVENTS = [:delete, :write, :extend, :attrib, :rename] # :link, :revoke
13
11
 
12
+ BSD_OS_REGEXP = /bsd|dragonfly/i
13
+
14
14
  # The message to show when wdm gem isn't available
15
15
  #
16
16
  BUNDLER_DECLARE_GEM = <<-EOS.gsub(/^ {6}/, '')
17
17
  Please add the following to your Gemfile to avoid polling for changes:
18
18
  require 'rbconfig'
19
- gem 'rb-kqueue', '>= 0.2' if RbConfig::CONFIG['target_os'] =~ /freebsd/i
19
+ if RbConfig::CONFIG['target_os'] =~ #{BSD_OS_REGEXP}
20
+ gem 'rb-kqueue', '>= 0.2'
21
+
22
+ # Base versions have known conflicts/bugs
23
+ # Even master branches may not work...
24
+ gem 'ffi', github: 'carpetsmoker/ffi', ref: 'ac63e07f7'
25
+ gem 'celluloid', github: 'celluloid/celluloid', ref: '7fdef04'
26
+ end
27
+ EOS
28
+
29
+ BSD_EXPERIMENTAL = <<-EOS.gsub(/^ {6}/, '')
30
+ NOTE *BSD SUPPORT IS EXPERIMENTAL!
31
+
32
+ In fact, it likely WONT WORK!!!!
33
+
34
+ (see: https://github.com/guard/listen/issues/220)
35
+
36
+ If you're brave enough, feel free to suggest pull requests and
37
+ experiment on your own. For help, browse existing issues marked 'bsd'
38
+ for clues, tips and workaround.
20
39
  EOS
21
40
 
22
41
  def self.usable?
23
- if RbConfig::CONFIG['target_os'] =~ /freebsd/i
42
+ if RbConfig::CONFIG['target_os'] =~ BSD_OS_REGEXP
43
+ Kernel.warn BSD_EXPERIMENTAL
24
44
  require 'rb-kqueue'
25
45
  require 'find'
26
46
  true
@@ -32,7 +52,7 @@ module Listen
32
52
 
33
53
  def start
34
54
  worker = _init_worker
35
- Thread.new { worker.poll }
55
+ Thread.new { worker.run }
36
56
  end
37
57
 
38
58
  private
@@ -45,27 +65,38 @@ module Listen
45
65
  def _init_worker
46
66
  KQueue::Queue.new.tap do |queue|
47
67
  _directories_path.each do |path|
48
- Find.find(path) { |file_path| _watch_file(file_path, queue) }
68
+ # use Record to make a snapshot of dir, so we
69
+ # can detect new files
70
+ _find(path) { |file_path| _watch_file(file_path, queue) }
49
71
  end
50
72
  end
51
73
  end
52
74
 
53
75
  def _worker_callback
54
76
  lambda do |event|
55
- _notify_change(_event_path(event), type: 'File', change: _change(event.flags))
77
+ change = _change(event.flags)
78
+ path = _event_path(event)
79
+ if path.directory?
80
+ # Force dir content tracking to kick in, or we won't have
81
+ # names of added files
82
+ _notify_change(path, type: 'Directory', recursive: true)
83
+ else
84
+ _notify_change(path, type: 'File', change: change)
85
+ end
56
86
 
57
- # If it is a directory, and it has a write flag, it means a
58
- # file has been added so find out which and deal with it.
59
- # No need to check for removed files, kqueue will forget them
60
- # when the vfs does.
61
- _watch_for_new_file(event) if _new_file_added?(event)
87
+ # If it is a directory, and it has a write flag, it means a
88
+ # file has been added so find out which and deal with it.
89
+ # No need to check for removed files, kqueue will forget them
90
+ # when the vfs does.
91
+ _watch_for_new_file(event) if _new_file_added?(event)
62
92
  end
63
93
  end
64
94
 
65
95
  def _change(event_flags)
66
96
  { modified: [:attrib, :extend],
67
97
  added: [:write],
68
- removed: [:rename, :delete] }.each do |change, flags|
98
+ removed: [:rename, :delete]
99
+ }.each do |change, flags|
69
100
  return change unless (flags & event_flags).empty?
70
101
  end
71
102
  nil
@@ -76,20 +107,26 @@ module Listen
76
107
  end
77
108
 
78
109
  def _new_file_added?(event)
79
- File.directory?(event.watcher.path) && event.flags.include?(:write)
110
+ ::File.directory?(event.watcher.path) && event.flags.include?(:write)
80
111
  end
81
112
 
82
113
  def _watch_for_new_file(event)
83
114
  queue = event.watcher.queue
84
- Find.find(path) do |file_path|
85
- _watch_file(file_path, queue) unless queue.watchers.detect { |k,v| v.path == file.to_s }
115
+ _find(_event_path(event).to_s) do |file_path|
116
+ unless queue.watchers.detect { |_, v| v.path == file_path.to_s }
117
+ _watch_file(file_path, queue)
118
+ end
86
119
  end
87
120
  end
88
121
 
89
122
  def _watch_file(path, queue)
90
123
  queue.watch_file(path, *EVENTS, &_worker_callback)
91
124
  end
92
- end
93
125
 
126
+ # Quick rubocop workaround
127
+ def _find(*paths)
128
+ Find.send(:find, *paths)
129
+ end
130
+ end
94
131
  end
95
132
  end
@@ -1,10 +1,8 @@
1
1
  module Listen
2
2
  module Adapter
3
-
4
3
  # Adapter implementation for Mac OS X `FSEvents`.
5
4
  #
6
5
  class Darwin < Base
7
-
8
6
  def self.usable?
9
7
  RbConfig::CONFIG['target_os'] =~ /darwin(1.+)?$/i
10
8
  end
@@ -27,7 +25,8 @@ module Listen
27
25
  def _init_worker
28
26
  FSEvent.new.tap do |worker|
29
27
  worker.watch(_directories_path, latency: _latency) do |changes|
30
- _changes_path(changes).each { |path| _notify_change(path, type: 'Dir') }
28
+ paths = _changes_path(changes)
29
+ paths.each { |path| _notify_change(path, type: 'Dir') }
31
30
  end
32
31
  end
33
32
  end
@@ -39,6 +38,5 @@ module Listen
39
38
  end
40
39
  end
41
40
  end
42
-
43
41
  end
44
42
  end
@@ -1,22 +1,24 @@
1
1
  module Listen
2
2
  module Adapter
3
-
4
3
  # Listener implementation for Linux `inotify`.
5
4
  #
6
5
  class Linux < Base
7
6
  # Watched inotify events
8
7
  #
9
8
  # @see http://www.tin.org/bin/man.cgi?section=7&topic=inotify
10
- # @see https://github.com/nex3/rb-inotify/blob/master/lib/rb-inotify/notifier.rb#L99-L177
9
+ # @see https://github.com/nex3/rb-inotify
11
10
  #
12
11
  EVENTS = [:recursive, :attrib, :create, :delete, :move, :close_write]
13
12
 
14
13
  # The message to show when the limit of inotify watchers is not enough
15
14
  #
15
+ WIKI_URL = 'https://github.com/guard/listen'\
16
+ '/wiki/Increasing-the-amount-of-inotify-watchers'
17
+
16
18
  INOTIFY_LIMIT_MESSAGE = <<-EOS.gsub(/^\s*/, '')
17
19
  FATAL: Listen error: unable to monitor directories for changes.
18
20
 
19
- Please head to https://github.com/guard/listen/wiki/Increasing-the-amount-of-inotify-watchers
21
+ Please head to #{WIKI_URL}
20
22
  for information on how to solve this issue.
21
23
  EOS
22
24
 
@@ -47,7 +49,9 @@ module Listen
47
49
  #
48
50
  def _init_worker
49
51
  INotify::Notifier.new.tap do |worker|
50
- _directories_path.each { |path| worker.watch(path, *EVENTS, &_worker_callback) }
52
+ _directories_path.each do |path|
53
+ worker.watch(path, *EVENTS, &_worker_callback)
54
+ end
51
55
  end
52
56
  end
53
57
 
@@ -58,24 +62,25 @@ module Listen
58
62
  path = _event_path(event)
59
63
  cookie_opts = event.cookie.zero? ? {} : { cookie: event.cookie }
60
64
 
61
- Celluloid.logger.info "listen: inotify event: #{event.flags.inspect}: #{event.name}"
65
+ _log(event)
62
66
 
63
67
  if _dir_event?(event)
64
- _notify_change(path, { type: 'Dir'}.merge(cookie_opts))
68
+ _notify_change(path, { type: 'Dir' }.merge(cookie_opts))
65
69
  else
66
- _notify_change(path, { type: 'File', change: _change(event.flags)}.merge(cookie_opts))
70
+ options = { type: 'File', change: _change(event.flags) }
71
+ _notify_change(path, options.merge(cookie_opts))
67
72
  end
68
73
  end
69
74
  end
70
75
 
71
76
  def _skip_event?(event)
72
77
  # Event on root directory
73
- return true if event.name == ""
78
+ return true if event.name == ''
74
79
  # INotify reports changes to files inside directories as events
75
80
  # on the directories themselves too.
76
81
  #
77
82
  # @see http://linux.die.net/man/7/inotify
78
- return true if _dir_event?(event) && (event.flags & [:close, :modify]).any?
83
+ _dir_event?(event) && (event.flags & [:close, :modify]).any?
79
84
  end
80
85
 
81
86
  def _change(event_flags)
@@ -96,7 +101,12 @@ module Listen
96
101
  def _event_path(event)
97
102
  Pathname.new(event.absolute_name)
98
103
  end
99
- end
100
104
 
105
+ def _log(event)
106
+ name = event.name
107
+ flags = event.flags.inspect
108
+ Celluloid.logger.info "inotify event: #{flags}: #{name}"
109
+ end
110
+ end
101
111
  end
102
112
  end
@@ -1,6 +1,5 @@
1
1
  module Listen
2
2
  module Adapter
3
-
4
3
  # Polling Adapter that works cross-platform and
5
4
  # has no dependencies. This is the adapter that
6
5
  # uses the most CPU processing power and has higher
@@ -44,6 +43,5 @@ module Listen
44
43
  sleep(nap_time) if nap_time > 0
45
44
  end
46
45
  end
47
-
48
46
  end
49
47
  end
@@ -2,7 +2,6 @@ require 'celluloid/io'
2
2
 
3
3
  module Listen
4
4
  module Adapter
5
-
6
5
  # Adapter to receive file system modifications over TCP
7
6
  class TCP < Base
8
7
  include Celluloid::IO
@@ -18,7 +17,7 @@ module Listen
18
17
  # Initializes and starts a Celluloid::IO-powered TCP-recipient
19
18
  def start
20
19
  @socket = TCPSocket.new(listener.host, listener.port)
21
- @buffer = String.new
20
+ @buffer = ''
22
21
  run
23
22
  end
24
23
 
@@ -36,7 +35,7 @@ module Listen
36
35
 
37
36
  # Continuously receive and asynchronously handle data
38
37
  def run
39
- while data = @socket.recv(RECEIVE_WINDOW)
38
+ while (data = @socket.recv(RECEIVE_WINDOW))
40
39
  async.handle_data(data)
41
40
  end
42
41
  end
@@ -44,7 +43,7 @@ module Listen
44
43
  # Buffers incoming data and handles messages accordingly
45
44
  def handle_data(data)
46
45
  @buffer << data
47
- while message = Listen::TCP::Message.from_buffer(@buffer)
46
+ while (message = Listen::TCP::Message.from_buffer(@buffer))
48
47
  handle_message(message)
49
48
  end
50
49
  end
@@ -58,7 +57,9 @@ module Listen
58
57
  end
59
58
  end
60
59
 
60
+ def self.local_fs?
61
+ false
62
+ end
61
63
  end
62
-
63
64
  end
64
65
  end
@@ -1,16 +1,16 @@
1
1
  module Listen
2
2
  module Adapter
3
-
4
3
  # Adapter implementation for Windows `wdm`.
5
4
  #
6
5
  class Windows < Base
7
-
8
6
  # The message to show when wdm gem isn't available
9
7
  #
10
8
  BUNDLER_DECLARE_GEM = <<-EOS.gsub(/^ {6}/, '')
11
9
  Please add the following to your Gemfile to avoid polling for changes:
12
10
  require 'rbconfig'
13
- gem 'wdm', '>= 0.1.0' if RbConfig::CONFIG['target_os'] =~ /mswin|mingw|cygwin/i
11
+ if RbConfig::CONFIG['target_os'] =~ /mswin|mingw|cygwin/i
12
+ gem 'wdm', '>= 0.1.0'
13
+ end
14
14
  EOS
15
15
 
16
16
  def self.usable?
@@ -37,13 +37,16 @@ module Listen
37
37
  #
38
38
  def _init_worker
39
39
  WDM::Monitor.new.tap do |worker|
40
- _directories_path.each { |path| worker.watch_recursively(path, &_worker_callback) }
40
+ _directories_path.each do |path|
41
+ worker.watch_recursively(path, &_worker_callback)
42
+ end
41
43
  end
42
44
  end
43
45
 
44
46
  def _worker_callback
45
47
  lambda do |change|
46
- _notify_change(_path(change.path), type: 'File', change: _change(change.type))
48
+ options = { type: 'File', change: _change(change.type) }
49
+ _notify_change(_path(change.path), options)
47
50
  end
48
51
  end
49
52
 
@@ -60,6 +63,5 @@ module Listen
60
63
  nil
61
64
  end
62
65
  end
63
-
64
66
  end
65
67
  end
data/lib/listen/change.rb CHANGED
@@ -16,7 +16,7 @@ module Listen
16
16
  cookie = options[:cookie]
17
17
 
18
18
  unless cookie
19
- #TODO: remove silencing here (it's done later)
19
+ # TODO: remove silencing here (it's done later)
20
20
  return if _silencer.silenced?(path, options[:type])
21
21
  end
22
22
 
@@ -47,6 +47,5 @@ module Listen
47
47
  def _silencer
48
48
  listener.registry[:silencer]
49
49
  end
50
-
51
50
  end
52
51
  end
data/lib/listen/cli.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'thor'
2
2
  require 'listen'
3
+ require 'logger'
3
4
 
4
5
  module Listen
5
6
  class CLI < Thor
@@ -8,44 +9,46 @@ module Listen
8
9
  desc 'start', 'Starts Listen'
9
10
 
10
11
  class_option :verbose,
11
- type: :boolean,
12
- default: false,
13
- aliases: '-v',
14
- banner: 'Verbose'
12
+ type: :boolean,
13
+ default: false,
14
+ aliases: '-v',
15
+ banner: 'Verbose'
15
16
 
16
17
  class_option :forward,
17
- type: :string,
18
- default: '127.0.0.1:4000',
19
- aliases: '-f',
20
- banner: 'The address to forward filesystem events'
18
+ type: :string,
19
+ default: '127.0.0.1:4000',
20
+ aliases: '-f',
21
+ banner: 'The address to forward filesystem events'
21
22
 
22
23
  class_option :directory,
23
- type: :string,
24
- default: '.',
25
- aliases: '-d',
26
- banner: 'The directory to listen to'
27
-
28
-
29
- def start
30
- Listen::Forwarder.new(options).start
31
- end
24
+ type: :string,
25
+ default: '.',
26
+ aliases: '-d',
27
+ banner: 'The directory to listen to'
32
28
 
29
+ def start
30
+ Listen::Forwarder.new(options).start
31
+ end
33
32
  end
34
33
 
35
34
  class Forwarder
35
+ attr_reader :logger
36
36
  def initialize(options)
37
37
  @options = options
38
+ @logger = Logger.new(STDOUT)
39
+ @logger.level = Logger::INFO
40
+ @logger.formatter = proc { |_, _, _, msg| "#{msg}\n" }
38
41
  end
39
42
 
40
43
  def start
41
- puts "Starting listen..."
44
+ logger.info 'Starting listen...'
42
45
  address = @options[:forward]
43
46
  directory = @options[:directory]
44
- callback = Proc.new do |modified, added, removed|
47
+ callback = proc do |modified, added, removed|
45
48
  if @options[:verbose]
46
- puts "+ #{added}" unless added.empty?
47
- puts "- #{removed}" unless removed.empty?
48
- puts "> #{modified}" unless modified.empty?
49
+ logger.info "+ #{added}" unless added.empty?
50
+ logger.info "- #{removed}" unless removed.empty?
51
+ logger.info "> #{modified}" unless modified.empty?
49
52
  end
50
53
  end
51
54