listen 2.10.1 → 3.0.0
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 +4 -4
- data/README.md +11 -46
- data/lib/listen.rb +19 -40
- data/lib/listen/adapter.rb +1 -3
- data/lib/listen/adapter/base.rb +66 -36
- data/lib/listen/adapter/config.rb +21 -0
- data/lib/listen/adapter/linux.rb +5 -7
- data/lib/listen/adapter/polling.rb +2 -1
- data/lib/listen/backend.rb +41 -0
- data/lib/listen/change.rb +45 -26
- data/lib/listen/cli.rb +3 -11
- data/lib/listen/directory.rb +31 -29
- data/lib/listen/event/config.rb +59 -0
- data/lib/listen/event/loop.rb +113 -0
- data/lib/listen/event/processor.rb +122 -0
- data/lib/listen/event/queue.rb +54 -0
- data/lib/listen/file.rb +9 -9
- data/lib/listen/fsm.rb +131 -0
- data/lib/listen/internals/thread_pool.rb +1 -1
- data/lib/listen/listener.rb +66 -305
- data/lib/listen/listener/config.rb +45 -0
- data/lib/listen/logger.rb +32 -0
- data/lib/listen/options.rb +1 -1
- data/lib/listen/queue_optimizer.rb +38 -20
- data/lib/listen/record.rb +50 -65
- data/lib/listen/silencer/controller.rb +48 -0
- data/lib/listen/version.rb +1 -1
- metadata +12 -21
- data/lib/listen/adapter/tcp.rb +0 -88
- data/lib/listen/internals/logging.rb +0 -35
- data/lib/listen/tcp.rb +0 -8
- data/lib/listen/tcp/broadcaster.rb +0 -79
- data/lib/listen/tcp/message.rb +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c4dbb678714e27c96553c3eeb428437dec7fdd06
|
4
|
+
data.tar.gz: 852d550603e6e96e47266c54d60a14cf2d680f01
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 080e123c08eb97243cf24d3dbb8f989c89fe797c5be172253d393b814550c50c66f765981125a825d47b61153c69715d6501b59d4f0bbabc36883d03c65623c1
|
7
|
+
data.tar.gz: a601fa4b17c2fcb5345fe4cb3fa8f10aaa28f6e31b22c5d456ede3dcf8ac8b641ee3c6b31fe53f411c5f69c3bcca8cd0a51b354e7ec778f08e5486d9081327e0
|
data/README.md
CHANGED
@@ -8,7 +8,10 @@ The Listen gem listens to file modifications and notifies you about the changes.
|
|
8
8
|
|
9
9
|
## Known issues / Quickfixes / Workarounds
|
10
10
|
|
11
|
-
|
11
|
+
*NOTE: TCP functionality has been removed from Listen 3.x - please use Listen
|
12
|
+
2.x until alternative server and client gems are created/released for 3.x.*
|
13
|
+
|
14
|
+
For other issues, just head over here: https://github.com/guard/listen/wiki/Quickfixes,-known-issues-and-workarounds
|
12
15
|
|
13
16
|
## Tips and Techniques
|
14
17
|
|
@@ -20,15 +23,17 @@ Make sure you know these few basic tricks: https://github.com/guard/listen/wiki/
|
|
20
23
|
* Detects file modification, addition and removal.
|
21
24
|
* You can watch multiple directories.
|
22
25
|
* Regexp-patterns for ignoring paths for more accuracy and speed
|
23
|
-
* Forwarding file events over TCP, [more info](#forwarding-file-events-over-tcp) below.
|
24
26
|
* Increased change detection accuracy on OS X HFS and VFAT volumes.
|
25
27
|
* Tested on MRI Ruby environments (1.9+ only) via [Travis CI](https://travis-ci.org/guard/listen),
|
26
28
|
|
27
|
-
|
29
|
+
NOTE: TCP functionality has been moved to a separate gem (listen-server and listen-client)
|
30
|
+
|
31
|
+
NOTES:
|
28
32
|
- Some filesystems won't work without polling (VM/Vagrant Shared folders, NFS, Samba, sshfs, etc.)
|
29
33
|
- Specs suite on JRuby and Rubinius aren't reliable on Travis CI, but should work.
|
30
34
|
- Windows and \*BSD adapter aren't continuously and automaticaly tested.
|
31
35
|
|
36
|
+
|
32
37
|
## Pending features / issues
|
33
38
|
|
34
39
|
* symlinked directories aren't fully transparent yet: https://github.com/guard/listen/issues/279
|
@@ -42,9 +47,10 @@ Pull request or help is very welcome for these.
|
|
42
47
|
The simplest way to install Listen is to use [Bundler](http://bundler.io).
|
43
48
|
|
44
49
|
```ruby
|
45
|
-
gem 'listen', '~>
|
50
|
+
gem 'listen', '~> 3.0' # NOTE: for TCP functionality, use '~> 2.10' for now
|
46
51
|
```
|
47
52
|
|
53
|
+
|
48
54
|
## Usage
|
49
55
|
|
50
56
|
Call `Listen.to` with either a single directory or multiple directories, then define the "changes" callback in a block.
|
@@ -166,7 +172,7 @@ force_polling: true # Force the use of the polling a
|
|
166
172
|
relative: false # Whether changes should be relative to current dir or not
|
167
173
|
# default: false
|
168
174
|
|
169
|
-
debug: true # Enable
|
175
|
+
debug: true # Enable Listen logger
|
170
176
|
# default: false
|
171
177
|
|
172
178
|
polling_fallback_message: 'custom message' # Set a custom polling fallback message (or disable it with false)
|
@@ -247,47 +253,6 @@ Also, if the directories you're watching contain many files, make sure you're:
|
|
247
253
|
|
248
254
|
When in doubt, LISTEN_GEM_DEBUGGING=2 can help discover the actual events and time they happened.
|
249
255
|
|
250
|
-
## Forwarding file events over TCP
|
251
|
-
|
252
|
-
Listen is capable of forwarding file events over the network using a messaging protocol. This can be useful for virtualized development environments when file events are unavailable, as is the case with shared folders in VMs.
|
253
|
-
|
254
|
-
[Vagrant](https://github.com/mitchellh/vagrant) uses Listen in it's rsync-auto mode to solve this issue.
|
255
|
-
|
256
|
-
To broadcast events over TCP programmatically, use the `forward_to` option with an address - just a port or a hostname/port combination:
|
257
|
-
|
258
|
-
```ruby
|
259
|
-
listener = Listen.to 'path/to/app', forward_to: '10.0.0.2:4000' do |modified, added, removed|
|
260
|
-
# After broadcasting the changes to any connected recipients,
|
261
|
-
# this block will still be called
|
262
|
-
end
|
263
|
-
listener.start
|
264
|
-
sleep
|
265
|
-
```
|
266
|
-
|
267
|
-
As a convenience, the `listen` script is supplied which listens to a directory and forwards the events to a network address
|
268
|
-
|
269
|
-
```bash
|
270
|
-
listen -f "10.0.0.2:4000" # changes in current directory are sent as absolute paths
|
271
|
-
listen -r -f "10.0.0.2:4000" # changes in current directory are sent as relative paths
|
272
|
-
listen -v -d "/projects/my_project" -f "10.0.0.2:4000" # changes in given directory are also shown
|
273
|
-
```
|
274
|
-
|
275
|
-
*NOTE: if you are using a gem like `guard` and the paths on host and guest are not exactly the same, you'll generally want to use the `-r` option for relative paths*
|
276
|
-
|
277
|
-
To connect to a broadcasting listener as a recipient, specify its address using `Listen.on`:
|
278
|
-
|
279
|
-
```ruby
|
280
|
-
listener = Listen.on '10.0.0.2:4000' do |modified, added, removed|
|
281
|
-
# This block will be called
|
282
|
-
end
|
283
|
-
listener.start
|
284
|
-
sleep
|
285
|
-
```
|
286
|
-
|
287
|
-
### Security considerations
|
288
|
-
|
289
|
-
Since file events potentially expose sensitive information, care must be taken when specifying the broadcaster address. It is recommended to **always** specify a hostname and make sure it is as specific as possible to reduce any undesirable eavesdropping.
|
290
|
-
|
291
256
|
## Development
|
292
257
|
|
293
258
|
* Documentation hosted at [RubyDoc](http://rubydoc.info/github/guard/listen/master/frames).
|
data/lib/listen.rb
CHANGED
@@ -1,16 +1,27 @@
|
|
1
|
-
require '
|
1
|
+
require 'logger'
|
2
|
+
require 'listen/logger'
|
2
3
|
require 'listen/listener'
|
3
4
|
|
4
5
|
require 'listen/internals/thread_pool'
|
5
6
|
|
7
|
+
# Always set up logging by default first time file is required
|
8
|
+
#
|
9
|
+
# NOTE: If you need to clear the logger completely, do so *after*
|
10
|
+
# requiring this file. If you need to set a custom logger,
|
11
|
+
# require the listen/logger file and set the logger before requiring
|
12
|
+
# this file.
|
13
|
+
Listen.setup_default_logger_if_unset
|
14
|
+
|
15
|
+
# Won't print anything by default because of level - unless you've set
|
16
|
+
# LISTEN_GEM_DEBUGGING or provided your own logger with a high enough level
|
17
|
+
Listen::Logger.info "Listen loglevel set to: #{Listen.logger.level}"
|
18
|
+
Listen::Logger.info "Listen version: #{Listen::VERSION}"
|
19
|
+
|
6
20
|
module Listen
|
7
21
|
class << self
|
8
22
|
# Listens to file system modifications on a either single directory or
|
9
23
|
# multiple directories.
|
10
24
|
#
|
11
|
-
# When :forward_to is specified, this listener will broadcast modifications
|
12
|
-
# over TCP.
|
13
|
-
#
|
14
25
|
# @param (see Listen::Listener#new)
|
15
26
|
#
|
16
27
|
# @yield [modified, added, removed] the changed files
|
@@ -21,18 +32,12 @@ module Listen
|
|
21
32
|
# @return [Listen::Listener] the listener
|
22
33
|
#
|
23
34
|
def to(*args, &block)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
_add_listener(*args, &block)
|
35
|
+
@listeners ||= []
|
36
|
+
Listener.new(*args, &block).tap do |listener|
|
37
|
+
@listeners << listener
|
38
|
+
end
|
29
39
|
end
|
30
40
|
|
31
|
-
# Stop all listeners & Celluloid
|
32
|
-
#
|
33
|
-
# Use it for testing purpose or when you are sure that Celluloid could be
|
34
|
-
# ended.
|
35
|
-
#
|
36
41
|
# This is used by the `listen` binary to handle Ctrl-C
|
37
42
|
#
|
38
43
|
def stop
|
@@ -45,32 +50,6 @@ module Listen
|
|
45
50
|
listener.stop
|
46
51
|
end
|
47
52
|
@listeners = nil
|
48
|
-
|
49
|
-
Celluloid.shutdown
|
50
|
-
end
|
51
|
-
|
52
|
-
# Listens to file system modifications broadcast over TCP.
|
53
|
-
#
|
54
|
-
# @param [String/Fixnum] target to listen on (hostname:port or port)
|
55
|
-
#
|
56
|
-
# @yield [modified, added, removed] the changed files
|
57
|
-
# @yieldparam [Array<String>] modified the list of modified files
|
58
|
-
# @yieldparam [Array<String>] added the list of added files
|
59
|
-
# @yieldparam [Array<String>] removed the list of removed files
|
60
|
-
#
|
61
|
-
# @return [Listen::Listener] the listener
|
62
|
-
#
|
63
|
-
def on(target, *args, &block)
|
64
|
-
_add_listener(target, :recipient, *args, &block)
|
65
|
-
end
|
66
|
-
|
67
|
-
private
|
68
|
-
|
69
|
-
def _add_listener(*args, &block)
|
70
|
-
@listeners ||= []
|
71
|
-
Listener.new(*args, &block).tap do |listener|
|
72
|
-
@listeners << listener
|
73
|
-
end
|
74
53
|
end
|
75
54
|
end
|
76
55
|
end
|
data/lib/listen/adapter.rb
CHANGED
@@ -12,8 +12,6 @@ module Listen
|
|
12
12
|
'Learn more at https://github.com/guard/listen#listen-adapters.'
|
13
13
|
|
14
14
|
def self.select(options = {})
|
15
|
-
_log :debug, 'Adapter: considering TCP ...'
|
16
|
-
return TCP if options[:force_tcp]
|
17
15
|
_log :debug, 'Adapter: considering polling ...'
|
18
16
|
return Polling if options[:force_polling]
|
19
17
|
_log :debug, 'Adapter: considering optimized backend...'
|
@@ -39,7 +37,7 @@ module Listen
|
|
39
37
|
end
|
40
38
|
|
41
39
|
def self._log(type, message)
|
42
|
-
|
40
|
+
Listen::Logger.send(type, message)
|
43
41
|
end
|
44
42
|
end
|
45
43
|
end
|
data/lib/listen/adapter/base.rb
CHANGED
@@ -1,32 +1,27 @@
|
|
1
1
|
require 'listen/options'
|
2
|
+
require 'listen/record'
|
3
|
+
require 'listen/change'
|
2
4
|
|
3
5
|
module Listen
|
4
6
|
module Adapter
|
5
7
|
class Base
|
6
|
-
include Celluloid
|
7
|
-
|
8
8
|
attr_reader :options
|
9
9
|
|
10
10
|
# TODO: only used by tests
|
11
11
|
DEFAULTS = {}
|
12
12
|
|
13
|
-
|
14
|
-
@configured = nil
|
15
|
-
options = opts.dup
|
16
|
-
@mq = options.delete(:mq)
|
17
|
-
@directories = options.delete(:directories)
|
13
|
+
attr_reader :config
|
18
14
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
15
|
+
def initialize(config)
|
16
|
+
@started = false
|
17
|
+
@config = config
|
23
18
|
|
24
|
-
|
25
|
-
|
26
|
-
|
19
|
+
@configured = nil
|
20
|
+
|
21
|
+
fail 'No directories to watch!' if config.directories.empty?
|
27
22
|
|
28
23
|
defaults = self.class.const_get('DEFAULTS')
|
29
|
-
@options = Listen::Options.new(
|
24
|
+
@options = Listen::Options.new(config.adapter_options, defaults)
|
30
25
|
rescue
|
31
26
|
_log_exception 'adapter config failed: %s:%s called from: %s', caller
|
32
27
|
raise
|
@@ -34,50 +29,82 @@ module Listen
|
|
34
29
|
|
35
30
|
# TODO: it's a separate method as a temporary workaround for tests
|
36
31
|
def configure
|
37
|
-
|
32
|
+
if @configured
|
33
|
+
_log(:warn, 'Adapter already configured!')
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
38
37
|
@configured = true
|
39
38
|
|
40
39
|
@callbacks ||= {}
|
41
|
-
|
42
|
-
unless dir.is_a?(Pathname)
|
43
|
-
fail ArgumentError, "not a Pathname: #{dir.inspect}"
|
44
|
-
end
|
45
|
-
|
40
|
+
config.directories.each do |dir|
|
46
41
|
callback = @callbacks[dir] || lambda do |event|
|
47
42
|
_process_event(dir, event)
|
48
43
|
end
|
49
44
|
@callbacks[dir] = callback
|
50
45
|
_configure(dir, &callback)
|
51
46
|
end
|
47
|
+
|
48
|
+
@snapshots ||= {}
|
49
|
+
# TODO: separate config per directory (some day maybe)
|
50
|
+
change_config = Change::Config.new(config.queue, config.silencer)
|
51
|
+
config.directories.each do |dir|
|
52
|
+
record = Record.new(dir)
|
53
|
+
snapshot = Change.new(change_config, record)
|
54
|
+
@snapshots[dir] = snapshot
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def started?
|
59
|
+
@started
|
52
60
|
end
|
53
61
|
|
54
62
|
def start
|
55
63
|
configure
|
64
|
+
|
65
|
+
if started?
|
66
|
+
_log(:warn, 'Adapter already started!')
|
67
|
+
return
|
68
|
+
end
|
69
|
+
|
70
|
+
@started = true
|
71
|
+
|
56
72
|
calling_stack = caller.dup
|
57
73
|
Listen::Internals::ThreadPool.add do
|
58
74
|
begin
|
75
|
+
@snapshots.values.each do |snapshot|
|
76
|
+
_timed('Record.build()') { snapshot.record.build }
|
77
|
+
end
|
59
78
|
_run
|
60
79
|
rescue
|
61
|
-
|
62
|
-
|
80
|
+
msg = 'run() in thread failed: %s:\n'\
|
81
|
+
' %s\n\ncalled from:\n %s'
|
82
|
+
_log_exception(msg, calling_stack)
|
83
|
+
raise # for unit tests mostly
|
63
84
|
end
|
64
85
|
end
|
65
86
|
end
|
66
87
|
|
67
|
-
def self.local_fs?
|
68
|
-
true
|
69
|
-
end
|
70
|
-
|
71
88
|
def self.usable?
|
72
89
|
const_get('OS_REGEXP') =~ RbConfig::CONFIG['target_os']
|
73
90
|
end
|
74
91
|
|
75
92
|
private
|
76
93
|
|
94
|
+
def _timed(title)
|
95
|
+
start = Time.now.to_f
|
96
|
+
yield
|
97
|
+
diff = Time.now.to_f - start
|
98
|
+
Listen::Logger.info format('%s: %.05f seconds', title, diff)
|
99
|
+
rescue
|
100
|
+
Listen::Logger.warn "#{title} crashed: #{$ERROR_INFO.inspect}"
|
101
|
+
raise
|
102
|
+
end
|
103
|
+
|
104
|
+
# TODO: allow backend adapters to pass specific invalidation objects
|
105
|
+
# e.g. Darwin -> DirRescan, INotify -> MoveScan, etc.
|
77
106
|
def _queue_change(type, dir, rel_path, options)
|
78
|
-
|
79
|
-
# Celluloid in tests
|
80
|
-
@mq.send(:_queue_raw_change, type, dir, rel_path, options)
|
107
|
+
@snapshots[dir].invalidate(type, rel_path, options)
|
81
108
|
end
|
82
109
|
|
83
110
|
def _log(*args, &block)
|
@@ -85,15 +112,18 @@ module Listen
|
|
85
112
|
end
|
86
113
|
|
87
114
|
def _log_exception(msg, caller_stack)
|
88
|
-
|
115
|
+
formatted = format(
|
116
|
+
msg,
|
117
|
+
$ERROR_INFO,
|
118
|
+
$ERROR_POSITION * "\n",
|
119
|
+
caller_stack * "\n"
|
120
|
+
)
|
121
|
+
|
122
|
+
_log(:error, formatted)
|
89
123
|
end
|
90
124
|
|
91
125
|
def self._log(*args, &block)
|
92
|
-
|
93
|
-
Celluloid::Logger.send(*args, block.call)
|
94
|
-
else
|
95
|
-
Celluloid::Logger.send(*args)
|
96
|
-
end
|
126
|
+
Listen::Logger.send(*args, &block)
|
97
127
|
end
|
98
128
|
end
|
99
129
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Listen
|
2
|
+
module Adapter
|
3
|
+
class Config
|
4
|
+
attr_reader :directories
|
5
|
+
attr_reader :silencer
|
6
|
+
attr_reader :queue
|
7
|
+
attr_reader :adapter_options
|
8
|
+
|
9
|
+
def initialize(directories, queue, silencer, adapter_options)
|
10
|
+
# TODO: fix (flatten, array, compact?)
|
11
|
+
@directories = directories.map do |directory|
|
12
|
+
Pathname.new(directory.to_s).realpath
|
13
|
+
end
|
14
|
+
|
15
|
+
@silencer = silencer
|
16
|
+
@queue = queue
|
17
|
+
@adapter_options = adapter_options
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/listen/adapter/linux.rb
CHANGED
@@ -12,7 +12,8 @@ module Listen
|
|
12
12
|
:delete,
|
13
13
|
:move,
|
14
14
|
:close_write
|
15
|
-
]
|
15
|
+
],
|
16
|
+
wait_for_delay: 0.1
|
16
17
|
}
|
17
18
|
|
18
19
|
private
|
@@ -26,13 +27,10 @@ module Listen
|
|
26
27
|
EOS
|
27
28
|
|
28
29
|
def _configure(directory, &callback)
|
29
|
-
require 'rb-inotify'
|
30
|
-
@worker ||= INotify::Notifier.new
|
30
|
+
Kernel.require 'rb-inotify'
|
31
|
+
@worker ||= ::INotify::Notifier.new
|
31
32
|
@worker.watch(directory.to_s, *options.events, &callback)
|
32
33
|
rescue Errno::ENOSPC
|
33
|
-
# workaround - Celluloid catches abort and prints nothing
|
34
|
-
STDERR.puts INOTIFY_LIMIT_MESSAGE
|
35
|
-
STDERR.flush
|
36
34
|
abort(INOTIFY_LIMIT_MESSAGE)
|
37
35
|
end
|
38
36
|
|
@@ -47,7 +45,7 @@ module Listen
|
|
47
45
|
path = Pathname.new(event.watcher.path) + event.name
|
48
46
|
rel_path = path.relative_path_from(dir).to_s
|
49
47
|
|
50
|
-
_log
|
48
|
+
_log(:debug) { "inotify: #{rel_path} (#{event.flags.inspect})" }
|
51
49
|
|
52
50
|
if /1|true/ =~ ENV['LISTEN_GEM_SIMULATE_FSEVENT']
|
53
51
|
if (event.flags & [:moved_to, :moved_from]) || _dir_event?(event)
|
@@ -8,7 +8,7 @@ module Listen
|
|
8
8
|
class Polling < Base
|
9
9
|
OS_REGEXP = // # match every OS
|
10
10
|
|
11
|
-
DEFAULTS = { latency: 1.0 }
|
11
|
+
DEFAULTS = { latency: 1.0, wait_for_delay: 0.05 }
|
12
12
|
|
13
13
|
private
|
14
14
|
|
@@ -23,6 +23,7 @@ module Listen
|
|
23
23
|
@polling_callbacks.each do |callback|
|
24
24
|
callback.call(nil)
|
25
25
|
nap_time = options.latency - (Time.now.to_f - start)
|
26
|
+
# TODO: warn if nap_time is negative (polling too slow)
|
26
27
|
sleep(nap_time) if nap_time > 0
|
27
28
|
end
|
28
29
|
end
|