listen 1.3.1 → 2.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,44 @@
1
+ module Listen
2
+ module Adapter
3
+
4
+ # Adapter implementation for Mac OS X `FSEvents`.
5
+ #
6
+ class Darwin < Base
7
+
8
+ def self.usable?
9
+ RbConfig::CONFIG['target_os'] =~ /darwin(1.+)?$/i
10
+ end
11
+
12
+ def initialize(listener)
13
+ require 'rb-fsevent'
14
+ super
15
+ end
16
+
17
+ def start
18
+ worker = _init_worker
19
+ worker.run
20
+ end
21
+
22
+ private
23
+
24
+ # Initializes a FSEvent worker and adds a watcher for
25
+ # each directory listened.
26
+ #
27
+ def _init_worker
28
+ FSEvent.new.tap do |worker|
29
+ worker.watch(_directories_path, latency: _latency) do |changes|
30
+ _changes_path(changes).each { |path| _notify_change(path, type: 'Dir') }
31
+ end
32
+ end
33
+ end
34
+
35
+ def _changes_path(changes)
36
+ changes.map do |path|
37
+ path.sub!(/\/$/, '')
38
+ Pathname.new(path)
39
+ end
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,92 @@
1
+ module Listen
2
+ module Adapter
3
+
4
+ # Listener implementation for Linux `inotify`.
5
+ #
6
+ class Linux < Base
7
+ # Watched inotify events
8
+ #
9
+ # @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
11
+ #
12
+ EVENTS = [:recursive, :attrib, :create, :delete, :move, :close_write]
13
+
14
+ # The message to show when the limit of inotify watchers is not enough
15
+ #
16
+ INOTIFY_LIMIT_MESSAGE = <<-EOS.gsub(/^\s*/, '')
17
+ Listen error: unable to monitor directories for changes.
18
+
19
+ Please head to https://github.com/guard/listen/wiki/Increasing-the-amount-of-inotify-watchers
20
+ for information on how to solve this issue.
21
+ EOS
22
+
23
+ def self.usable?
24
+ RbConfig::CONFIG['target_os'] =~ /linux/i
25
+ end
26
+
27
+ def initialize(listener)
28
+ require 'rb-inotify'
29
+ super
30
+ end
31
+
32
+ def start
33
+ worker = _init_worker
34
+ worker.run
35
+ rescue Errno::ENOSPC
36
+ abort(INOTIFY_LIMIT_MESSAGE)
37
+ end
38
+
39
+ private
40
+
41
+ # Initializes a INotify worker and adds a watcher for
42
+ # each directory passed to the adapter.
43
+ #
44
+ # @return [INotify::Notifier] initialized worker
45
+ def _init_worker
46
+ INotify::Notifier.new.tap do |worker|
47
+ _directories_path.each { |path| worker.watch(path, *EVENTS, &_worker_callback) }
48
+ end
49
+ end
50
+
51
+ def _worker_callback
52
+ lambda do |event|
53
+ next if _skip_event?(event)
54
+
55
+ if _dir_event?(event)
56
+ _notify_change(_event_path(event), type: 'Dir')
57
+ else
58
+ _notify_change(_event_path(event), type: 'file', change: _change(event.flags))
59
+ end
60
+ end
61
+ end
62
+
63
+ def _skip_event?(event)
64
+ # Event on root directory
65
+ return true if event.name == ""
66
+ # INotify reports changes to files inside directories as events
67
+ # on the directories themselves too.
68
+ #
69
+ # @see http://linux.die.net/man/7/inotify
70
+ return true if _dir_event?(event) && (event.flags & [:close, :modify]).any?
71
+ end
72
+
73
+ def _change(event_flags)
74
+ { modified: [:attrib],
75
+ added: [:moved_to, :create],
76
+ removed: [:moved_from, :delete] }.each do |change, flags|
77
+ return change unless (flags & event_flags).empty?
78
+ end
79
+ nil
80
+ end
81
+
82
+ def _dir_event?(event)
83
+ event.flags.include?(:isdir)
84
+ end
85
+
86
+ def _event_path(event)
87
+ Pathname.new(event.absolute_name)
88
+ end
89
+ end
90
+
91
+ end
92
+ end
@@ -0,0 +1,49 @@
1
+ module Listen
2
+ module Adapter
3
+
4
+ # Polling Adapter that works cross-platform and
5
+ # has no dependencies. This is the adapter that
6
+ # uses the most CPU processing power and has higher
7
+ # file IO than the other implementations.
8
+ #
9
+ class Polling < Base
10
+ DEFAULT_POLLING_LATENCY = 1.0
11
+
12
+ def self.usable?
13
+ true
14
+ end
15
+
16
+ def start
17
+ _poll_directories
18
+ end
19
+
20
+ private
21
+
22
+ def _latency
23
+ listener.options[:latency] || DEFAULT_POLLING_LATENCY
24
+ end
25
+
26
+ def _poll_directories
27
+ _napped_loop do
28
+ listener.directories.each do |path|
29
+ _notify_change(path, type: 'Dir', recursive: true)
30
+ end
31
+ end
32
+ end
33
+
34
+ def _napped_loop
35
+ loop do
36
+ _nap_time { yield }
37
+ end
38
+ end
39
+
40
+ def _nap_time
41
+ start = Time.now.to_f
42
+ yield
43
+ nap_time = _latency - (Time.now.to_f - start)
44
+ sleep(nap_time) if nap_time > 0
45
+ end
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,63 @@
1
+ module Listen
2
+ module Adapter
3
+
4
+ # Adapter implementation for Windows `wdm`.
5
+ #
6
+ class Windows < Base
7
+
8
+ # The message to show when wdm gem isn't available
9
+ #
10
+ BUNDLER_DECLARE_GEM = <<-EOS.gsub(/^ {6}/, '')
11
+ Please add the following to your Gemfile to avoid polling for changes:
12
+ require 'rbconfig'
13
+ gem 'wdm', '>= 0.1.0' if RbConfig::CONFIG['target_os'] =~ /mswin|mingw|cygwin/i
14
+ EOS
15
+
16
+ def self.usable?
17
+ if RbConfig::CONFIG['target_os'] =~ /mswin|mingw|cygwin/i
18
+ require 'wdm'
19
+ true
20
+ end
21
+ rescue Gem::LoadError
22
+ Kernel.warn BUNDLER_DECLARE_GEM
23
+ end
24
+
25
+ def start
26
+ worker = _init_worker
27
+ worker.run!
28
+ end
29
+
30
+ private
31
+
32
+ # Initializes a WDM monitor and adds a watcher for
33
+ # each directory passed to the adapter.
34
+ #
35
+ # @return [WDM::Monitor] initialized worker
36
+ def _init_worker
37
+ WDM::Monitor.new.tap do |worker|
38
+ _directories_path.each { |path| worker.watch_recursively(path, &_worker_callback) }
39
+ end
40
+ end
41
+
42
+ def _worker_callback
43
+ lambda do |change|
44
+ _notify_change(_path(change.path), type: 'file', change: _change(change.type))
45
+ end
46
+ end
47
+
48
+ def _path(path)
49
+ Pathname.new(path)
50
+ end
51
+
52
+ def _change(type)
53
+ { modified: [:modified],
54
+ added: [:added, :renamed_new_file],
55
+ removed: [:removed, :renamed_old_file] }.each do |change, types|
56
+ return change if types.include?(type)
57
+ end
58
+ nil
59
+ end
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,42 @@
1
+ require 'listen/file'
2
+ require 'listen/directory'
3
+ require 'listen/silencer'
4
+
5
+ module Listen
6
+ class Change
7
+ include Celluloid
8
+
9
+ attr_accessor :listener, :silencer
10
+
11
+ def initialize(listener)
12
+ @listener = listener
13
+ @silencer = Silencer.new(listener.options)
14
+ end
15
+
16
+ def change(path, options)
17
+ return if silencer.silenced?(path)
18
+ if change = options[:change]
19
+ _notify_listener(change, path)
20
+ else
21
+ send("_#{options[:type].downcase}_change", path, options)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def _file_change(path, options)
28
+ change = File.new(path).change
29
+ if change && listener.listen? && !options[:silence]
30
+ _notify_listener(change, path)
31
+ end
32
+ end
33
+
34
+ def _dir_change(path, options)
35
+ Directory.new(path, options).scan
36
+ end
37
+
38
+ def _notify_listener(change, path)
39
+ listener.changes << { change => path }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,73 @@
1
+ module Listen
2
+ class Directory
3
+ attr_accessor :path, :options
4
+
5
+ def initialize(path, options = {})
6
+ @path = path
7
+ @options = options
8
+ end
9
+
10
+ def scan
11
+ _update_record
12
+ _all_entries.each do |entry_path, data|
13
+ case data[:type]
14
+ when 'File' then _async_change(entry_path, options.merge(type: 'File'))
15
+ when 'Dir'
16
+ _async_change(entry_path, options.merge(type: 'Dir')) if _recursive_scan?(entry_path)
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def _update_record
24
+ if ::Dir.exists?(path)
25
+ _record.async.set_path(path, { type: 'Dir'})
26
+ else
27
+ _record.async.unset_path(path)
28
+ end
29
+ end
30
+
31
+ def _all_entries
32
+ _record_entries.merge(_entries)
33
+ end
34
+
35
+ def _entries
36
+ return {} unless ::Dir.exists?(path)
37
+ entries = ::Dir.entries(path) - %w[. ..]
38
+ entries = entries.map { |entry| [entry, type: _entry_type(entry)] }
39
+ Hash[*entries.flatten]
40
+ end
41
+
42
+ def _entry_type(entry_path)
43
+ entry_path = path.join(entry_path)
44
+ if entry_path.file?
45
+ 'File'
46
+ elsif entry_path.directory?
47
+ 'Dir'
48
+ end
49
+ end
50
+
51
+ def _record_entries
52
+ future = _record.future.dir_entries(path)
53
+ future.value
54
+ end
55
+
56
+ def _record
57
+ Celluloid::Actor[:listen_record]
58
+ end
59
+
60
+ def _change_pool
61
+ Celluloid::Actor[:listen_change_pool]
62
+ end
63
+
64
+ def _recursive_scan?(path)
65
+ !::Dir.exists?(path) || options[:recursive]
66
+ end
67
+
68
+ def _async_change(entry_path, options)
69
+ entry_path = path.join(entry_path)
70
+ _change_pool.async.change(entry_path, options)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,108 @@
1
+ module Listen
2
+ class File
3
+ attr_accessor :path, :data
4
+
5
+ def initialize(path)
6
+ @path = path
7
+ @data = { type: 'File' }
8
+ end
9
+
10
+ def change
11
+ if _existing_path? && _modified?
12
+ _set_record_data
13
+ :modified
14
+ elsif _new_path?
15
+ _set_record_data
16
+ :added
17
+ elsif _removed_path?
18
+ _unset_record_data
19
+ :removed
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def _new_path?
26
+ _exist? && !_record_data?
27
+ end
28
+
29
+ def _existing_path?
30
+ _exist? && _record_data?
31
+ end
32
+
33
+ def _removed_path?
34
+ !_exist?
35
+ end
36
+
37
+ def _record_data?
38
+ !_record_data.empty?
39
+ end
40
+
41
+ def _exist?
42
+ @exist ||= ::File.exist?(path)
43
+ end
44
+
45
+ def _modified?
46
+ _mtime > _record_data[:mtime] || _mode_modified? || _content_modified?
47
+ end
48
+
49
+ def _mode_modified?
50
+ _mode != _record_data[:mode]
51
+ end
52
+
53
+ # Only useful on Darwin because of the file mtime second precision
54
+ #
55
+ def _content_modified?
56
+ _record_data[:md5] && _md5 != _record_data[:md5]
57
+ end
58
+
59
+ def _set_record_data
60
+ @data.merge!(_new_data)
61
+ _record.async.set_path(path, data)
62
+ end
63
+
64
+ def _unset_record_data
65
+ _record.async.unset_path(path)
66
+ end
67
+
68
+ # Only Darwin need md5 comparaison because of the file mtime second precision
69
+ #
70
+ def _new_data
71
+ data = { mtime: _mtime, mode: _mode }
72
+ data[:md5] = _md5 if RbConfig::CONFIG['target_os'] =~ /darwin/i
73
+ data
74
+ end
75
+
76
+ def _record_data
77
+ @_record_data ||= _record.future.file_data(path).value
78
+ end
79
+
80
+ def _record
81
+ Celluloid::Actor[:listen_record]
82
+ end
83
+
84
+ def _mtime
85
+ @mtime ||= _lstat.mtime.to_f
86
+ rescue
87
+ 0.0
88
+ end
89
+
90
+ def _mode
91
+ @mode ||= _lstat.mode
92
+ rescue
93
+ nil
94
+ end
95
+
96
+ def _lstat
97
+ @lstat ||= ::File.lstat(path)
98
+ rescue
99
+ nil
100
+ end
101
+
102
+ def _md5
103
+ @md5 ||= Digest::MD5.file(path).digest
104
+ rescue
105
+ nil
106
+ end
107
+ end
108
+ end