listen 0.1.0 → 0.2.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.
- data/CHANGELOG.md +10 -2
- data/README.md +71 -35
- data/lib/listen.rb +1 -1
- data/lib/listen/adapter.rb +22 -1
- data/lib/listen/adapters/darwin.rb +58 -0
- data/lib/listen/adapters/linux.rb +85 -0
- data/lib/listen/adapters/polling.rb +6 -5
- data/lib/listen/adapters/windows.rb +75 -0
- data/lib/listen/listener.rb +111 -35
- data/lib/listen/version.rb +1 -1
- metadata +25 -64
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
## Master
|
2
2
|
|
3
|
+
- Add checksum comparaison support for detecting consecutive file modifications made during the same second. ([@thibaudgg][])
|
4
|
+
- Add rb-fchange support. ([@thibaudgg][])
|
5
|
+
- Add rb-inotify support. ([@thibaudgg][] with [@Maher4Ever][] help)
|
6
|
+
- Add rb-fsevent support. ([@thibaudgg][])
|
7
|
+
- Add non-recursive diff with multiple directories support. ([@thibaudgg][])
|
8
|
+
- Ignore .DS_Store by default. ([@thibaudgg][])
|
9
|
+
|
3
10
|
## 0.1.0 - January 28, 2012
|
4
11
|
|
5
|
-
- First version with only a polling adapter and basic features set. ([@thibaudgg][])
|
12
|
+
- First version with only a polling adapter and basic features set (ignore & filter). ([@thibaudgg][])
|
6
13
|
|
7
|
-
[@thibaudgg]: https://github.com/thibaudgg
|
14
|
+
[@thibaudgg]: https://github.com/thibaudgg
|
15
|
+
[@Maher4Ever]: https://github.com/Maher4Ever
|
data/README.md
CHANGED
@@ -1,63 +1,99 @@
|
|
1
1
|
# Listen [](http://travis-ci.org/guard/listen)
|
2
2
|
|
3
|
-
Work in progress
|
3
|
+
**Work in progress...**
|
4
4
|
|
5
5
|
The Listen gem listens to file modifications and notifies you about the changes.
|
6
6
|
|
7
|
-
|
7
|
+
## TODO
|
8
8
|
|
9
|
-
|
9
|
+
- **DONE** Add polling support
|
10
|
+
- **DONE** Add `rb-fsevent` support
|
11
|
+
- **DONE** Add `rb-inotify` support
|
12
|
+
- **DONE** Add `rb-fchange` support
|
13
|
+
- **DONE** Add checksum comparaison support for detecting consecutive file modifications made during the same second. (like Guard)
|
14
|
+
- Add latency option
|
15
|
+
- Add polling option (true: polling forced, false: polling never used (to skip DropBox polling fallback))
|
16
|
+
- Dropbox detection with polling fallback (if needed)
|
17
|
+
- Improve API (if needed)
|
10
18
|
|
11
|
-
|
19
|
+
## Install
|
12
20
|
|
13
|
-
```
|
14
|
-
|
15
|
-
...
|
16
|
-
end
|
21
|
+
``` bash
|
22
|
+
gem install listen
|
17
23
|
```
|
18
24
|
|
19
|
-
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
There are two ways you can use Listen.
|
28
|
+
|
29
|
+
1. call `Listen.to` with a path params, and define callbacks in a block.
|
30
|
+
3. create a `listener` object usable in an (ARel style) chainable way.
|
31
|
+
|
32
|
+
Feel free to give your feeback via [Listen issues](https://github.com/guard/listener/issues)
|
33
|
+
|
34
|
+
### Block API
|
35
|
+
|
36
|
+
#### One dir
|
20
37
|
|
21
38
|
``` ruby
|
22
|
-
Listen.to do
|
23
|
-
|
24
|
-
...
|
25
|
-
end
|
26
|
-
path(dir2, filter: '**/*', ignore: paths) do |modified, added, removed|
|
27
|
-
...
|
28
|
-
end
|
29
|
-
....
|
39
|
+
Listen.to('dir/path/to/listen', filter: /.*\.rb/, ignore: '/ignored/path') do |modified, added, removed|
|
40
|
+
# ...
|
30
41
|
end
|
31
42
|
```
|
32
43
|
|
33
|
-
|
34
|
-
|
35
|
-
## "Object" API
|
44
|
+
### "Object" API
|
36
45
|
|
37
46
|
``` ruby
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
listen.start # enter the run loop
|
45
|
-
listen.stop
|
47
|
+
listener = Listen.to('dir/path/to/listen')
|
48
|
+
listener = listener.ignore('/ignored/path')
|
49
|
+
listener = listener.filter(/.*\.rb/)
|
50
|
+
listener = listener.change(&callback)
|
51
|
+
listener.start # enter the run loop
|
52
|
+
listener.stop
|
46
53
|
```
|
47
54
|
|
48
|
-
|
55
|
+
#### Chainable
|
49
56
|
|
50
57
|
``` ruby
|
51
|
-
Listen.to(dir).ignore('
|
58
|
+
Listen.to('dir/path/to/listen').ignore('/ignored/path').filter(/.*\.rb/).change(&callback).start # enter the run loop
|
52
59
|
```
|
53
60
|
|
54
|
-
|
61
|
+
#### Multiple listeners support available via Thread
|
55
62
|
|
56
63
|
``` ruby
|
57
|
-
|
58
|
-
styles
|
59
|
-
scripts
|
64
|
+
listener = Listen.to(dir1).ignore('/ignored/path/')
|
65
|
+
styles = listener.filter(/.*\.css/).change(&style_callback)
|
66
|
+
scripts = listener.filter(/.*\.js/).change(&scripts_callback)
|
60
67
|
|
61
68
|
Thread.new { styles.start } # enter the run loop
|
62
69
|
Thread.new { scripts.start } # enter the run loop
|
63
|
-
```
|
70
|
+
```
|
71
|
+
|
72
|
+
### Options
|
73
|
+
|
74
|
+
These options can be set through `Listen.to` params or via methods (see the "Object" API)
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
:filter => /.*\.rb/, /.*\.coffee/ # Filter files to listen to via a regexps list.
|
78
|
+
# default: none
|
79
|
+
|
80
|
+
:ignore => 'path1', 'path2' # Ignore a list of paths (root directory or sub-dir)
|
81
|
+
# default: '.bundle', '.git', '.DS_Store', 'log', 'tmp', 'vendor'
|
82
|
+
```
|
83
|
+
|
84
|
+
## Acknowledgment
|
85
|
+
|
86
|
+
- [Travis Tilley (ttilley)][] for this awesome work on [fssm][] & [rb-fsevent][].
|
87
|
+
- [Nathan Weizenbaum (nex3)][] for [rb-inotify][], a thorough inotify wrapper.
|
88
|
+
- [stereobooster][] for [rb-fchange][], windows support wouldn't exist without him.
|
89
|
+
- [Yehuda Katz (wycats)][] for [vigilo][], that has been a great source of inspiration.
|
90
|
+
|
91
|
+
[Travis Tilley (ttilley)]: https://github.com/ttilley
|
92
|
+
[fssm]: https://github.com/ttilley/fssm
|
93
|
+
[rb-fsevent]: https://github.com/thibaudgg/rb-fsevent
|
94
|
+
[Nathan Weizenbaum (nex3)]: https://github.com/nex3
|
95
|
+
[rb-inotify]: https://github.com/nex3/rb-inotify
|
96
|
+
[stereobooster]: https://github.com/stereobooster
|
97
|
+
[rb-fchange]: https://github.com/stereobooster/rb-fchange
|
98
|
+
[Yehuda Katz (wycats)]: https://github.com/wycats
|
99
|
+
[vigilo]: https://github.com/wycats/vigilo
|
data/lib/listen.rb
CHANGED
data/lib/listen/adapter.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
|
1
3
|
module Listen
|
2
4
|
class Adapter
|
5
|
+
attr_accessor :latency
|
3
6
|
|
4
7
|
# Select the appropriate adapter implementation for the
|
5
8
|
# current OS and initializes it.
|
@@ -7,12 +10,30 @@ module Listen
|
|
7
10
|
# @return [Listen::Adapter] the chosen adapter
|
8
11
|
#
|
9
12
|
def self.select_and_initialize(listener)
|
10
|
-
Adapters::
|
13
|
+
if Adapters::Darwin.usable?
|
14
|
+
Adapters::Darwin.new(listener)
|
15
|
+
elsif Adapters::Linux.usable?
|
16
|
+
Adapters::Linux.new(listener)
|
17
|
+
elsif Adapters::Windows.usable?
|
18
|
+
Adapters::Windows.new(listener)
|
19
|
+
else
|
20
|
+
Adapters::Polling.new(listener)
|
21
|
+
end
|
11
22
|
end
|
12
23
|
|
13
24
|
def initialize(listener)
|
14
25
|
@listener = listener
|
15
26
|
end
|
16
27
|
|
28
|
+
# Start the adapter.
|
29
|
+
#
|
30
|
+
def start
|
31
|
+
end
|
32
|
+
|
33
|
+
# Stop the adapter.
|
34
|
+
#
|
35
|
+
def stop
|
36
|
+
end
|
37
|
+
|
17
38
|
end
|
18
39
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Listen
|
2
|
+
module Adapters
|
3
|
+
|
4
|
+
# Adapter implementation for Mac OS X `FSEvents`.
|
5
|
+
#
|
6
|
+
class Darwin < Adapter
|
7
|
+
|
8
|
+
# Initialize the Adapter.
|
9
|
+
#
|
10
|
+
def initialize(*)
|
11
|
+
super
|
12
|
+
@latency ||= 0.1
|
13
|
+
init_worker
|
14
|
+
end
|
15
|
+
|
16
|
+
# Start the adapter.
|
17
|
+
#
|
18
|
+
def start
|
19
|
+
super
|
20
|
+
@worker.run
|
21
|
+
end
|
22
|
+
|
23
|
+
# Stop the adapter.
|
24
|
+
#
|
25
|
+
def stop
|
26
|
+
super
|
27
|
+
@worker.stop
|
28
|
+
end
|
29
|
+
|
30
|
+
# Check if the adapter is usable on the current OS.
|
31
|
+
#
|
32
|
+
# @return [Boolean] whether usable or not
|
33
|
+
#
|
34
|
+
def self.usable?
|
35
|
+
return false unless RbConfig::CONFIG['target_os'] =~ /darwin(1.+)?$/i
|
36
|
+
|
37
|
+
require 'rb-fsevent'
|
38
|
+
true
|
39
|
+
rescue LoadError
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# Initialiaze FSEvent worker and set watch callback block
|
46
|
+
#
|
47
|
+
def init_worker
|
48
|
+
@worker = FSEvent.new
|
49
|
+
@worker.watch(@listener.directory, :latency => @latency) do |changed_dirs|
|
50
|
+
changed_dirs.map! { |path| path.sub /\/$/, '' }
|
51
|
+
@listener.on_change(changed_dirs)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Listen
|
4
|
+
module Adapters
|
5
|
+
|
6
|
+
# Watched INotify EVENTS
|
7
|
+
#
|
8
|
+
# @see http://www.tin.org/bin/man.cgi?section=7&topic=inotify
|
9
|
+
# @see https://github.com/nex3/rb-inotify/blob/master/lib/rb-inotify/notifier.rb#L99-L177
|
10
|
+
#
|
11
|
+
EVENTS = %w[recursive attrib close modify move create delete delete_self move_self]
|
12
|
+
|
13
|
+
# Listener implementation for Linux `inotify`.
|
14
|
+
#
|
15
|
+
class Linux < Adapter
|
16
|
+
|
17
|
+
# Initialize the Adapter.
|
18
|
+
#
|
19
|
+
def initialize(*)
|
20
|
+
super
|
21
|
+
@latency ||= 0.1
|
22
|
+
@changed_dirs = Set.new
|
23
|
+
init_worker
|
24
|
+
end
|
25
|
+
|
26
|
+
# Start the adapter.
|
27
|
+
#
|
28
|
+
def start
|
29
|
+
super
|
30
|
+
Thread.new { @worker.run }
|
31
|
+
@stop = false
|
32
|
+
poll_changed_dirs
|
33
|
+
end
|
34
|
+
|
35
|
+
# Stop the adapter.
|
36
|
+
#
|
37
|
+
def stop
|
38
|
+
super
|
39
|
+
@stop = true
|
40
|
+
@worker.stop
|
41
|
+
end
|
42
|
+
|
43
|
+
# Check if the adapter is usable on the current OS.
|
44
|
+
#
|
45
|
+
# @return [Boolean] whether usable or not
|
46
|
+
#
|
47
|
+
def self.usable?
|
48
|
+
return false unless RbConfig::CONFIG['target_os'] =~ /linux/i
|
49
|
+
|
50
|
+
require 'rb-inotify'
|
51
|
+
true
|
52
|
+
rescue LoadError
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Initialiaze INotify worker and set watch callback block.
|
59
|
+
#
|
60
|
+
def init_worker
|
61
|
+
@worker = INotify::Notifier.new
|
62
|
+
@worker.watch(@listener.directory, *EVENTS.map(&:to_sym)) do |event|
|
63
|
+
unless event.name == "" # Event on root directory
|
64
|
+
@changed_dirs << File.dirname(event.absolute_name)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Polling around @changed_dirs presence.
|
70
|
+
#
|
71
|
+
def poll_changed_dirs
|
72
|
+
until @stop
|
73
|
+
sleep(@latency)
|
74
|
+
|
75
|
+
next if @changed_dirs.empty?
|
76
|
+
changed_dirs = @changed_dirs.to_a
|
77
|
+
@changed_dirs.clear
|
78
|
+
@listener.on_change(changed_dirs)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -7,18 +7,18 @@ module Listen
|
|
7
7
|
# file IO that the other implementations.
|
8
8
|
#
|
9
9
|
class Polling < Adapter
|
10
|
-
|
11
|
-
|
10
|
+
|
12
11
|
# Initialize the Adapter.
|
13
12
|
#
|
14
13
|
def initialize(*)
|
15
14
|
super
|
16
|
-
@latency
|
15
|
+
@latency ||= 1.0
|
17
16
|
end
|
18
17
|
|
19
18
|
# Start the adapter.
|
20
19
|
#
|
21
20
|
def start
|
21
|
+
super
|
22
22
|
@stop = false
|
23
23
|
poll
|
24
24
|
end
|
@@ -26,17 +26,18 @@ module Listen
|
|
26
26
|
# Stop the adapter.
|
27
27
|
#
|
28
28
|
def stop
|
29
|
+
super
|
29
30
|
@stop = true
|
30
31
|
end
|
31
32
|
|
32
|
-
|
33
|
+
private
|
33
34
|
|
34
35
|
# Poll listener directory for file system changes.
|
35
36
|
#
|
36
37
|
def poll
|
37
38
|
until @stop
|
38
39
|
start = Time.now.to_f
|
39
|
-
@listener.on_change(@listener.directory)
|
40
|
+
@listener.on_change([@listener.directory], :recursive => true)
|
40
41
|
nap_time = @latency - (Time.now.to_f - start)
|
41
42
|
sleep(nap_time) if nap_time > 0
|
42
43
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Listen
|
4
|
+
module Adapters
|
5
|
+
|
6
|
+
# Adapter implementation for Windows `fchange`.
|
7
|
+
#
|
8
|
+
class Windows < Adapter
|
9
|
+
|
10
|
+
# Initialize the Adapter.
|
11
|
+
#
|
12
|
+
def initialize(*)
|
13
|
+
super
|
14
|
+
@latency ||= 0.1
|
15
|
+
@changed_dirs = Set.new
|
16
|
+
init_worker
|
17
|
+
end
|
18
|
+
|
19
|
+
# Start the adapter.
|
20
|
+
#
|
21
|
+
def start
|
22
|
+
super
|
23
|
+
Thread.new { @worker.run }
|
24
|
+
@stop = false
|
25
|
+
poll_changed_dirs
|
26
|
+
end
|
27
|
+
|
28
|
+
# Stop the adapter.
|
29
|
+
#
|
30
|
+
def stop
|
31
|
+
super
|
32
|
+
@worker.stop
|
33
|
+
end
|
34
|
+
|
35
|
+
# Check if the adapter is usable on the current OS.
|
36
|
+
#
|
37
|
+
# @return [Boolean] whether usable or not
|
38
|
+
#
|
39
|
+
def self.usable?
|
40
|
+
return false unless RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
|
41
|
+
|
42
|
+
require 'rb-fchange'
|
43
|
+
true
|
44
|
+
rescue LoadError
|
45
|
+
false
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# Initialiaze FSEvent worker and set watch callback block
|
51
|
+
#
|
52
|
+
def init_worker
|
53
|
+
@worker = FChange::Notifier.new
|
54
|
+
@worker.watch(@listener.directory, :all_events, :recursive) do |event|
|
55
|
+
@changed_dirs << File.expand_path(event.watcher.path)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Polling around @changed_dirs presence.
|
60
|
+
#
|
61
|
+
def poll_changed_dirs
|
62
|
+
until @stop
|
63
|
+
sleep(@latency)
|
64
|
+
|
65
|
+
next if @changed_dirs.empty?
|
66
|
+
changed_dirs = @changed_dirs.to_a
|
67
|
+
@changed_dirs.clear
|
68
|
+
@listener.on_change(changed_dirs, :recursive => true)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
data/lib/listen/listener.rb
CHANGED
@@ -1,13 +1,18 @@
|
|
1
1
|
require 'find'
|
2
|
+
require 'digest/sha1'
|
3
|
+
|
2
4
|
require 'listen/adapter'
|
5
|
+
require 'listen/adapters/darwin'
|
6
|
+
require 'listen/adapters/linux'
|
3
7
|
require 'listen/adapters/polling'
|
8
|
+
require 'listen/adapters/windows'
|
4
9
|
|
5
10
|
module Listen
|
6
11
|
class Listener
|
7
|
-
attr_accessor :directory, :ignored_paths, :file_filters, :paths
|
12
|
+
attr_accessor :directory, :ignored_paths, :file_filters, :sha1_checksums, :paths, :adapter
|
8
13
|
|
9
14
|
# Default paths that gets ignored by the listener
|
10
|
-
DEFAULT_IGNORED_PATHS = %w[.bundle .git log tmp vendor]
|
15
|
+
DEFAULT_IGNORED_PATHS = %w[.bundle .git .DS_Store log tmp vendor]
|
11
16
|
|
12
17
|
# Initialize the file listener.
|
13
18
|
#
|
@@ -24,10 +29,11 @@ module Listen
|
|
24
29
|
# @return [Listen::Listener] the file listener
|
25
30
|
#
|
26
31
|
def initialize(*args, &block)
|
27
|
-
@directory
|
28
|
-
@ignored_paths
|
29
|
-
@file_filters
|
30
|
-
@
|
32
|
+
@directory = args.first
|
33
|
+
@ignored_paths = DEFAULT_IGNORED_PATHS
|
34
|
+
@file_filters = []
|
35
|
+
@sha1_checksums = {}
|
36
|
+
@block = block
|
31
37
|
if args[1]
|
32
38
|
@ignored_paths += Array(args[1][:ignore]) if args[1][:ignore]
|
33
39
|
@file_filters += Array(args[1][:filter]) if args[1][:filter]
|
@@ -93,31 +99,38 @@ module Listen
|
|
93
99
|
|
94
100
|
# Call @block callback when there is a diff in the passed directory.
|
95
101
|
#
|
96
|
-
# @param [
|
102
|
+
# @param [Array] directories the list of directories to diff
|
97
103
|
#
|
98
|
-
def on_change(
|
99
|
-
changes = diff(
|
100
|
-
unless changes
|
104
|
+
def on_change(directories, diff_options = {})
|
105
|
+
changes = diff(directories, diff_options)
|
106
|
+
unless changes.values.all? { |paths| paths.empty? }
|
101
107
|
@block.call(changes[:modified],changes[:added],changes[:removed])
|
102
108
|
end
|
103
109
|
end
|
104
110
|
|
105
|
-
# Initialize the @paths double levels Hash with all existing paths.
|
111
|
+
# Initialize the @paths double levels Hash with all existing paths and set diffed_at.
|
106
112
|
#
|
107
113
|
def init_paths
|
108
114
|
@paths = Hash.new { |h,k| h[k] = {} }
|
109
115
|
all_existing_paths { |path| insert_path(path) }
|
116
|
+
@diffed_at = Time.now.to_i
|
110
117
|
end
|
111
118
|
|
112
119
|
# Detect changes diff in a directory.
|
113
120
|
#
|
114
|
-
# @param [
|
121
|
+
# @param [Array] directories the list of directories to diff
|
122
|
+
# @param [Hash] options
|
123
|
+
# @option options [Boolean] recursive scan all sub-direcoties recursively (true when polling)
|
115
124
|
# @return [Hash<Array>] the file changes
|
116
125
|
#
|
117
|
-
def diff(
|
118
|
-
@changes
|
119
|
-
|
120
|
-
|
126
|
+
def diff(directories, options = {})
|
127
|
+
@changes = { :modified => [], :added => [], :removed => [] }
|
128
|
+
directories = directories.sort_by { |el| el.length }.reverse # diff sub-dir first
|
129
|
+
directories.each do |directory|
|
130
|
+
detect_modifications_and_removals(directory, options)
|
131
|
+
detect_additions(directory, options)
|
132
|
+
end
|
133
|
+
@diffed_at = Time.now.to_i
|
121
134
|
@changes
|
122
135
|
end
|
123
136
|
|
@@ -129,62 +142,107 @@ module Listen
|
|
129
142
|
#
|
130
143
|
def all_existing_paths
|
131
144
|
Find.find(@directory) do |path|
|
145
|
+
next if @directory == path
|
146
|
+
|
132
147
|
if File.directory?(path)
|
133
|
-
if
|
148
|
+
if ignored_path?(path)
|
134
149
|
Find.prune # Don't look any further into this directory.
|
135
150
|
else
|
136
151
|
yield(path)
|
137
152
|
end
|
138
|
-
elsif
|
153
|
+
elsif !ignored_path?(path) && filtered_file?(path)
|
139
154
|
yield(path)
|
140
155
|
end
|
141
156
|
end
|
142
157
|
end
|
143
158
|
|
144
|
-
# Insert a path with its File
|
159
|
+
# Insert a path with its type (Dir or File) in @paths.
|
145
160
|
#
|
146
161
|
# @param [String] path the path to insert in @paths.
|
147
162
|
#
|
148
163
|
def insert_path(path)
|
149
|
-
@paths[File.dirname(path)][File.basename(path)] = File.
|
164
|
+
@paths[File.dirname(path)][File.basename(path)] = File.directory?(path) ? 'Dir' : 'File'
|
165
|
+
end
|
166
|
+
|
167
|
+
# Find is a path exists in @paths.
|
168
|
+
#
|
169
|
+
# @param [String] path the path to find in @paths.
|
170
|
+
# @return [Boolean]
|
171
|
+
#
|
172
|
+
def existing_path?(path)
|
173
|
+
@paths[File.dirname(path)][File.basename(path)] != nil
|
150
174
|
end
|
151
175
|
|
152
176
|
# Detect modifications and removals recursivly in a directory.
|
177
|
+
# Modifications detection are based on mtime first and on checksum when mtime == last diffed_at
|
153
178
|
#
|
154
179
|
# @param [String] directory the path to analyze
|
180
|
+
# @param [Hash] options
|
181
|
+
# @option options [Boolean] recursive scan all sub-direcoties recursively (when polling)
|
155
182
|
#
|
156
|
-
def detect_modifications_and_removals(directory)
|
157
|
-
@paths[directory].each do |basename,
|
183
|
+
def detect_modifications_and_removals(directory, options = {})
|
184
|
+
@paths[directory].each do |basename, type|
|
158
185
|
path = File.join(directory, basename)
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
186
|
+
|
187
|
+
case type
|
188
|
+
when 'Dir'
|
189
|
+
if File.directory?(path)
|
190
|
+
detect_modifications_and_removals(path, options) if options[:recursive]
|
191
|
+
else
|
192
|
+
detect_modifications_and_removals(path, :recursive => true)
|
193
|
+
@paths[directory].delete(basename)
|
194
|
+
@paths.delete("#{directory}/#{basename}")
|
195
|
+
end
|
196
|
+
when 'File'
|
164
197
|
if File.exist?(path)
|
165
|
-
|
166
|
-
if
|
198
|
+
new_mtime = File.mtime(path).to_i
|
199
|
+
if @diffed_at < new_mtime || (@diffed_at == new_mtime && content_modified?(path))
|
167
200
|
@changes[:modified] << relative_path(path)
|
168
|
-
@paths[directory][basename] = new_stat
|
169
201
|
end
|
170
202
|
else
|
171
203
|
@paths[directory].delete(basename)
|
204
|
+
@sha1_checksums.delete(path)
|
172
205
|
@changes[:removed] << relative_path(path)
|
173
206
|
end
|
174
207
|
end
|
175
208
|
end
|
176
209
|
end
|
177
210
|
|
211
|
+
# Tests if the file content has been modified by
|
212
|
+
# comparing the SHA1 checksum.
|
213
|
+
#
|
214
|
+
# @param [String] path the file path
|
215
|
+
#
|
216
|
+
def content_modified?(path)
|
217
|
+
sha1_checksum = Digest::SHA1.file(path).to_s
|
218
|
+
if @sha1_checksums[path] != sha1_checksum
|
219
|
+
@sha1_checksums[path] = sha1_checksum
|
220
|
+
true
|
221
|
+
else
|
222
|
+
false
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
178
226
|
# Detect additions in a directory.
|
179
227
|
#
|
180
228
|
# @param [String] directory the path to analyze
|
229
|
+
# @param [Hash] options
|
230
|
+
# @option options [Boolean] recursive scan all sub-direcoties recursively (when polling)
|
181
231
|
#
|
182
|
-
def detect_additions(directory)
|
183
|
-
|
184
|
-
next if @
|
232
|
+
def detect_additions(directory, options = {})
|
233
|
+
Find.find(directory) do |path|
|
234
|
+
next if @directory == path
|
185
235
|
|
186
|
-
|
187
|
-
|
236
|
+
if File.directory?(path)
|
237
|
+
if directory != path && (ignored_path?(path) || (!options[:recursive] && existing_path?(path)))
|
238
|
+
Find.prune # Don't look any further into this directory.
|
239
|
+
else
|
240
|
+
insert_path(path)
|
241
|
+
end
|
242
|
+
elsif !existing_path?(path) && !ignored_path?(path) && filtered_file?(path)
|
243
|
+
@changes[:added] << relative_path(path) if File.file?(path)
|
244
|
+
insert_path(path)
|
245
|
+
end
|
188
246
|
end
|
189
247
|
end
|
190
248
|
|
@@ -199,5 +257,23 @@ module Listen
|
|
199
257
|
path.sub(%r(^#{base_dir}/), '')
|
200
258
|
end
|
201
259
|
|
260
|
+
# Test if a path should be ignored or not.
|
261
|
+
#
|
262
|
+
# @param [String] path the path to test.
|
263
|
+
# @return [Boolean]
|
264
|
+
#
|
265
|
+
def ignored_path?(path)
|
266
|
+
@ignored_paths.any? { |ignored_path| path =~ /#{ignored_path}$/ }
|
267
|
+
end
|
268
|
+
|
269
|
+
# Test if a file path should be filtered or not.
|
270
|
+
#
|
271
|
+
# @param [String] path the file path to test.
|
272
|
+
# @return [Boolean]
|
273
|
+
#
|
274
|
+
def filtered_file?(path)
|
275
|
+
@file_filters.empty? || @file_filters.any? { |file_filter| path =~ file_filter }
|
276
|
+
end
|
277
|
+
|
202
278
|
end
|
203
279
|
end
|
data/lib/listen/version.rb
CHANGED
metadata
CHANGED
@@ -1,89 +1,53 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: listen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
|
-
- Travis Tilley
|
9
|
-
- Yehuda Katz
|
10
8
|
- Thibaud Guillaume-Gentil
|
11
|
-
- Rémy Coutable
|
12
9
|
- Michael Kessler
|
13
10
|
autorequire:
|
14
11
|
bindir: bin
|
15
12
|
cert_chain: []
|
16
|
-
date: 2012-
|
13
|
+
date: 2012-02-12 00:00:00.000000000 Z
|
17
14
|
dependencies:
|
18
15
|
- !ruby/object:Gem::Dependency
|
19
|
-
name:
|
20
|
-
requirement: &
|
21
|
-
none: false
|
22
|
-
requirements:
|
23
|
-
- - ! '>='
|
24
|
-
- !ruby/object:Gem::Version
|
25
|
-
version: '0'
|
26
|
-
type: :development
|
27
|
-
prerelease: false
|
28
|
-
version_requirements: *70178027214260
|
29
|
-
- !ruby/object:Gem::Dependency
|
30
|
-
name: guard
|
31
|
-
requirement: &70178027229800 !ruby/object:Gem::Requirement
|
16
|
+
name: rb-fsevent
|
17
|
+
requirement: &70294412755220 !ruby/object:Gem::Requirement
|
32
18
|
none: false
|
33
19
|
requirements:
|
34
20
|
- - ~>
|
35
21
|
- !ruby/object:Gem::Version
|
36
|
-
version:
|
37
|
-
type: :
|
22
|
+
version: 0.9.0
|
23
|
+
type: :runtime
|
38
24
|
prerelease: false
|
39
|
-
version_requirements: *
|
25
|
+
version_requirements: *70294412755220
|
40
26
|
- !ruby/object:Gem::Dependency
|
41
|
-
name:
|
42
|
-
requirement: &
|
27
|
+
name: rb-inotify
|
28
|
+
requirement: &70294412770880 !ruby/object:Gem::Requirement
|
43
29
|
none: false
|
44
30
|
requirements:
|
45
31
|
- - ~>
|
46
32
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
48
|
-
type: :
|
33
|
+
version: 0.8.8
|
34
|
+
type: :runtime
|
49
35
|
prerelease: false
|
50
|
-
version_requirements: *
|
36
|
+
version_requirements: *70294412770880
|
51
37
|
- !ruby/object:Gem::Dependency
|
52
|
-
name:
|
53
|
-
requirement: &
|
38
|
+
name: rb-fchange
|
39
|
+
requirement: &70294412770160 !ruby/object:Gem::Requirement
|
54
40
|
none: false
|
55
41
|
requirements:
|
56
42
|
- - ~>
|
57
43
|
- !ruby/object:Gem::Version
|
58
|
-
version: 0.
|
59
|
-
type: :
|
44
|
+
version: 0.0.5
|
45
|
+
type: :runtime
|
60
46
|
prerelease: false
|
61
|
-
version_requirements: *
|
47
|
+
version_requirements: *70294412770160
|
62
48
|
- !ruby/object:Gem::Dependency
|
63
|
-
name:
|
64
|
-
requirement: &
|
65
|
-
none: false
|
66
|
-
requirements:
|
67
|
-
- - ! '>='
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: '0'
|
70
|
-
type: :development
|
71
|
-
prerelease: false
|
72
|
-
version_requirements: *70178027227420
|
73
|
-
- !ruby/object:Gem::Dependency
|
74
|
-
name: redcarpet
|
75
|
-
requirement: &70178027226740 !ruby/object:Gem::Requirement
|
76
|
-
none: false
|
77
|
-
requirements:
|
78
|
-
- - ! '>='
|
79
|
-
- !ruby/object:Gem::Version
|
80
|
-
version: '0'
|
81
|
-
type: :development
|
82
|
-
prerelease: false
|
83
|
-
version_requirements: *70178027226740
|
84
|
-
- !ruby/object:Gem::Dependency
|
85
|
-
name: pry
|
86
|
-
requirement: &70178027226280 !ruby/object:Gem::Requirement
|
49
|
+
name: bundler
|
50
|
+
requirement: &70294412769580 !ruby/object:Gem::Requirement
|
87
51
|
none: false
|
88
52
|
requirements:
|
89
53
|
- - ! '>='
|
@@ -91,21 +55,21 @@ dependencies:
|
|
91
55
|
version: '0'
|
92
56
|
type: :development
|
93
57
|
prerelease: false
|
94
|
-
version_requirements: *
|
58
|
+
version_requirements: *70294412769580
|
95
59
|
description: The Listen gem listens to file modifications and notifies you about the
|
96
|
-
changes.
|
60
|
+
changes. Works everywhere!
|
97
61
|
email:
|
98
|
-
- ttilley@gmail.com
|
99
|
-
- wycats@gmail.com
|
100
62
|
- thibaud@thibaud.me
|
101
|
-
- rymai@rymai.me
|
102
63
|
- michi@netzpiraten.ch
|
103
64
|
executables: []
|
104
65
|
extensions: []
|
105
66
|
extra_rdoc_files: []
|
106
67
|
files:
|
107
68
|
- lib/listen/adapter.rb
|
69
|
+
- lib/listen/adapters/darwin.rb
|
70
|
+
- lib/listen/adapters/linux.rb
|
108
71
|
- lib/listen/adapters/polling.rb
|
72
|
+
- lib/listen/adapters/windows.rb
|
109
73
|
- lib/listen/listener.rb
|
110
74
|
- lib/listen/version.rb
|
111
75
|
- lib/listen.rb
|
@@ -124,9 +88,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
124
88
|
- - ! '>='
|
125
89
|
- !ruby/object:Gem::Version
|
126
90
|
version: '0'
|
127
|
-
segments:
|
128
|
-
- 0
|
129
|
-
hash: -4340389000594607932
|
130
91
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
92
|
none: false
|
132
93
|
requirements:
|
@@ -135,7 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
135
96
|
version: 1.3.6
|
136
97
|
requirements: []
|
137
98
|
rubyforge_project: listen
|
138
|
-
rubygems_version: 1.8.
|
99
|
+
rubygems_version: 1.8.15
|
139
100
|
signing_key:
|
140
101
|
specification_version: 3
|
141
102
|
summary: Listen to file modifications
|