listen 0.5.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,167 +1,173 @@
1
- ## 0.5.1 - Septemper 18, 2012
2
-
3
- ### Bug fixes
4
-
5
- - [#61] Fix a synchronisation bug that caused constant fallback to polling. (fixed by [@Maher4Ever][])
6
-
7
- ## 0.5.0 - Septemper 1, 2012
8
-
9
- ### New features
10
-
11
- - Add a dependency manager to handle platform-specific gems. So there is no need anymore to install
12
- extra gems which will never be used on the user system. ([@Maher4Ever][])
13
- - Add a manual reporting mode to the adapters. ([@Maher4Ever][])
14
-
15
- ### Improvements
16
-
17
- - [#28] Enhance the speed of detecting changes on Windows by using the [WDM][] library. ([@Maher4Ever][])
18
-
19
- ## 0.4.7 - June 27, 2012
20
-
21
- ### Bug fixes
22
-
23
- - Increase latency to 0.25, to avoid useless polling fallback. (fixed by [@thibaudgg][])
24
- - Change watched inotify events, to avoid duplication callback. (fixed by [@thibaudgg][])
25
- - [#41](https://github.com/guard/listen/issues/41) Use lstat instead of stat when calculating mtime. (fixed by [@ebroder][])
26
-
27
- ## 0.4.6 - June 20, 2012
28
-
29
- ### Bug fix
30
-
31
- - [#39](https://github.com/guard/listen/issues/39) Fix digest race condition. (fixed by [@dkubb][])
32
-
33
- ## 0.4.5 - June 13, 2012
34
-
35
- ### Bug fix
36
-
37
- - [#39](https://github.com/guard/listen/issues/39) Rescue Errno::ENOENT when path inserted doesn't exist. (reported by [@textgoeshere][], fixed by [@thibaudgg][] and [@rymai][])
38
-
39
- ## 0.4.4 - June 8, 2012
40
-
41
- ### Bug fixes
42
-
43
- - ~~[#39](https://github.com/guard/listen/issues/39) Non-existing path insertion bug. (reported by [@textgoeshere][], fixed by [@thibaudgg][])~~
44
- - Fix relative path for directories containing special characters. (reported by [@napcs][], fixed by [@netzpirat][])
45
-
46
- ## 0.4.3 - June 6, 2012
47
-
48
- ### Bug fixes
49
-
50
- - [#24](https://github.com/guard/listen/issues/24) Fail gracefully when the inotify limit is not enough for Listen to function. (reported by [@daemonza][], fixed by [@Maher4Ever][])
51
- - [#32](https://github.com/guard/listen/issues/32) Fix a crash when trying to calculate the checksum of unreadable files. (reported by [@nex3][], fixed by [@Maher4Ever][])
52
-
53
- ### Improvements
54
-
55
- - Add `#relative_paths` method to listeners. ([@Maher4Ever][])
56
- - Add `#started?` query-method to adapters. ([@Maher4Ever][])
57
- - Dynamically detect the mtime precision used on a system. ([@Maher4Ever][] with help from [@nex3][])
58
-
59
- ## 0.4.2 - May 1, 2012
60
-
61
- ### Bug fixes
62
-
63
- - [#21](https://github.com/guard/listen/issues/21) Issues when listening to changes in relative paths. (reported by [@akerbos][], fixed by [@Maher4Ever][])
64
- - [#27](https://github.com/guard/listen/issues/27) Wrong reports for files modifications. (reported by [@cobychapple][], fixed by [@Maher4Ever][])
65
- - Fix segmentation fault when profiling on Windows. ([@Maher4Ever][])
66
- - Fix redundant watchers on Windows. ([@Maher4Ever][])
67
-
68
- ### Improvements
69
-
70
- - [#17](https://github.com/guard/listen/issues/17) Use regexp-patterns with the `ignore` method instead of supplying paths. (reported by [@fny][], added by [@Maher4Ever][])
71
- - Speed improvement when listening to changes in directories with ignored paths. ([@Maher4Ever][])
72
- - Added `.rbx` and `.svn` to ignored directories. ([@Maher4Ever][])
73
-
74
- ## 0.4.1 - April 15, 2012
75
-
76
- ### Bug fix
77
-
78
- - [#18](https://github.com/guard/listen/issues/18) Listener crashes when removing directories with nested paths. (reported by [@daemonza][], fixed by [@Maher4Ever][])
79
-
80
- ## 0.4.0 - April 9, 2012
81
-
82
- ### New features
83
-
84
- - Add `wait_for_callback` method to all adapters. ([@Maher4Ever][])
85
- - Add `Listen::MultiListener` class to listen to multiple directories at once. ([@Maher4Ever][])
86
- - Allow passing multiple directories to the `Listen.to` method. ([@Maher4Ever][])
87
- - Add `blocking` option to `Listen#start` which can be used to disable blocking the current thread upon starting. ([@Maher4Ever][])
88
- - Use absolute-paths in callbacks by default instead of relative-paths. ([@Maher4Ever][])
89
- - Add `relative_paths` option to `Listen::Listener` to retain the old functionality. ([@Maher4Ever][])
90
-
91
- ### Improvements
92
-
93
- - Encapsulate thread spawning in the linux-adapter. ([@Maher4Ever][])
94
- - Encapsulate thread spawning in the darwin-adapter. ([@Maher4Ever][] with [@scottdavis][] help)
95
- - Encapsulate thread spawning in the windows-adapter. ([@Maher4Ever][])
96
- - Fix linux-adapter bug where Listen would report file-modification events on the parent-directory. ([@Maher4Ever][])
97
-
98
- ### Change
99
-
100
- - Remove `wait_until_listening` as adapters doesn't need to run inside threads anymore ([@Maher4Ever][])
101
-
102
- ## 0.3.3 - March 6, 2012
103
-
104
- ### Improvement
105
-
106
- - Improve pause/unpause. ([@thibaudgg][])
107
-
108
- ## 0.3.2 - March 4, 2012
109
-
110
- ### New feature
111
-
112
- - Add pause/unpause listener's methods. ([@thibaudgg][])
113
-
114
- ## 0.3.1 - February 22, 2012
115
-
116
- ### Bug fix
117
-
118
- - [#9](https://github.com/guard/listen/issues/9) Ignore doesn't seem to work. (reported by [@markiz][], fixed by [@thibaudgg][])
119
-
120
- ## 0.3.0 - February 21, 2012
121
-
122
- ### New features
123
-
124
- - Add automatic fallback to polling if system adapter doesn't work (like a DropBox folder). ([@thibaudgg][])
125
- - Add latency and force_polling options. ([@Maher4Ever][])
126
-
127
- ## 0.2.0 - February 13, 2012
128
-
129
- ### New features
130
-
131
- - Add checksum comparaison support for detecting consecutive file modifications made during the same second. ([@thibaudgg][])
132
- - Add rb-fchange support. ([@thibaudgg][])
133
- - Add rb-inotify support. ([@thibaudgg][] with [@Maher4Ever][] help)
134
- - Add rb-fsevent support. ([@thibaudgg][])
135
- - Add non-recursive diff with multiple directories support. ([@thibaudgg][])
136
- - Ignore .DS_Store by default. ([@thibaudgg][])
137
-
138
- ## 0.1.0 - January 28, 2012
139
-
140
- - First version with only a polling adapter and basic features set (ignore & filter). ([@thibaudgg][])
141
-
142
- <!--- The following link definition list is generated by PimpMyChangelog --->
143
- [#9]: https://github.com/guard/listen/issues/9
144
- [#17]: https://github.com/guard/listen/issues/17
145
- [#18]: https://github.com/guard/listen/issues/18
146
- [#21]: https://github.com/guard/listen/issues/21
147
- [#24]: https://github.com/guard/listen/issues/24
148
- [#27]: https://github.com/guard/listen/issues/27
149
- [#28]: https://github.com/guard/listen/issues/28
150
- [#32]: https://github.com/guard/listen/issues/32
151
- [#39]: https://github.com/guard/listen/issues/39
152
- [@Maher4Ever]: https://github.com/Maher4Ever
153
- [@dkubb]: https://github.com/dkubb
154
- [@ebroder]: https://github.com/ebroder
155
- [@akerbos]: https://github.com/akerbos
156
- [@cobychapple]: https://github.com/cobychapple
157
- [@daemonza]: https://github.com/daemonza
158
- [@fny]: https://github.com/fny
159
- [@markiz]: https://github.com/markiz
160
- [@napcs]: https://github.com/napcs
161
- [@netzpirat]: https://github.com/netzpirat
162
- [@nex3]: https://github.com/nex3
163
- [@rymai]: https://github.com/rymai
164
- [@scottdavis]: https://github.com/scottdavis
165
- [@textgoeshere]: https://github.com/textgoeshere
166
- [@thibaudgg]: https://github.com/thibaudgg
167
- [WDM]: https://github.com/Maher4Ever/wdm
1
+ ## 0.5.2 - Septemper 23, 2012
2
+
3
+ ### Bug fix
4
+
5
+ - [#62] Fix double change callback with polling adapter. (fixed by [@thibaudgg][])
6
+
7
+ ## 0.5.1 - Septemper 18, 2012
8
+
9
+ ### Bug fix
10
+
11
+ - [#61] Fix a synchronisation bug that caused constant fallback to polling. (fixed by [@Maher4Ever][])
12
+
13
+ ## 0.5.0 - Septemper 1, 2012
14
+
15
+ ### New features
16
+
17
+ - Add a dependency manager to handle platform-specific gems. So there is no need anymore to install
18
+ extra gems which will never be used on the user system. ([@Maher4Ever][])
19
+ - Add a manual reporting mode to the adapters. ([@Maher4Ever][])
20
+
21
+ ### Improvements
22
+
23
+ - [#28] Enhance the speed of detecting changes on Windows by using the [WDM][] library. ([@Maher4Ever][])
24
+
25
+ ## 0.4.7 - June 27, 2012
26
+
27
+ ### Bug fixes
28
+
29
+ - Increase latency to 0.25, to avoid useless polling fallback. (fixed by [@thibaudgg][])
30
+ - Change watched inotify events, to avoid duplication callback. (fixed by [@thibaudgg][])
31
+ - [#41](https://github.com/guard/listen/issues/41) Use lstat instead of stat when calculating mtime. (fixed by [@ebroder][])
32
+
33
+ ## 0.4.6 - June 20, 2012
34
+
35
+ ### Bug fix
36
+
37
+ - [#39](https://github.com/guard/listen/issues/39) Fix digest race condition. (fixed by [@dkubb][])
38
+
39
+ ## 0.4.5 - June 13, 2012
40
+
41
+ ### Bug fix
42
+
43
+ - [#39](https://github.com/guard/listen/issues/39) Rescue Errno::ENOENT when path inserted doesn't exist. (reported by [@textgoeshere][], fixed by [@thibaudgg][] and [@rymai][])
44
+
45
+ ## 0.4.4 - June 8, 2012
46
+
47
+ ### Bug fixes
48
+
49
+ - ~~[#39](https://github.com/guard/listen/issues/39) Non-existing path insertion bug. (reported by [@textgoeshere][], fixed by [@thibaudgg][])~~
50
+ - Fix relative path for directories containing special characters. (reported by [@napcs][], fixed by [@netzpirat][])
51
+
52
+ ## 0.4.3 - June 6, 2012
53
+
54
+ ### Bug fixes
55
+
56
+ - [#24](https://github.com/guard/listen/issues/24) Fail gracefully when the inotify limit is not enough for Listen to function. (reported by [@daemonza][], fixed by [@Maher4Ever][])
57
+ - [#32](https://github.com/guard/listen/issues/32) Fix a crash when trying to calculate the checksum of unreadable files. (reported by [@nex3][], fixed by [@Maher4Ever][])
58
+
59
+ ### Improvements
60
+
61
+ - Add `#relative_paths` method to listeners. ([@Maher4Ever][])
62
+ - Add `#started?` query-method to adapters. ([@Maher4Ever][])
63
+ - Dynamically detect the mtime precision used on a system. ([@Maher4Ever][] with help from [@nex3][])
64
+
65
+ ## 0.4.2 - May 1, 2012
66
+
67
+ ### Bug fixes
68
+
69
+ - [#21](https://github.com/guard/listen/issues/21) Issues when listening to changes in relative paths. (reported by [@akerbos][], fixed by [@Maher4Ever][])
70
+ - [#27](https://github.com/guard/listen/issues/27) Wrong reports for files modifications. (reported by [@cobychapple][], fixed by [@Maher4Ever][])
71
+ - Fix segmentation fault when profiling on Windows. ([@Maher4Ever][])
72
+ - Fix redundant watchers on Windows. ([@Maher4Ever][])
73
+
74
+ ### Improvements
75
+
76
+ - [#17](https://github.com/guard/listen/issues/17) Use regexp-patterns with the `ignore` method instead of supplying paths. (reported by [@fny][], added by [@Maher4Ever][])
77
+ - Speed improvement when listening to changes in directories with ignored paths. ([@Maher4Ever][])
78
+ - Added `.rbx` and `.svn` to ignored directories. ([@Maher4Ever][])
79
+
80
+ ## 0.4.1 - April 15, 2012
81
+
82
+ ### Bug fix
83
+
84
+ - [#18](https://github.com/guard/listen/issues/18) Listener crashes when removing directories with nested paths. (reported by [@daemonza][], fixed by [@Maher4Ever][])
85
+
86
+ ## 0.4.0 - April 9, 2012
87
+
88
+ ### New features
89
+
90
+ - Add `wait_for_callback` method to all adapters. ([@Maher4Ever][])
91
+ - Add `Listen::MultiListener` class to listen to multiple directories at once. ([@Maher4Ever][])
92
+ - Allow passing multiple directories to the `Listen.to` method. ([@Maher4Ever][])
93
+ - Add `blocking` option to `Listen#start` which can be used to disable blocking the current thread upon starting. ([@Maher4Ever][])
94
+ - Use absolute-paths in callbacks by default instead of relative-paths. ([@Maher4Ever][])
95
+ - Add `relative_paths` option to `Listen::Listener` to retain the old functionality. ([@Maher4Ever][])
96
+
97
+ ### Improvements
98
+
99
+ - Encapsulate thread spawning in the linux-adapter. ([@Maher4Ever][])
100
+ - Encapsulate thread spawning in the darwin-adapter. ([@Maher4Ever][] with [@scottdavis][] help)
101
+ - Encapsulate thread spawning in the windows-adapter. ([@Maher4Ever][])
102
+ - Fix linux-adapter bug where Listen would report file-modification events on the parent-directory. ([@Maher4Ever][])
103
+
104
+ ### Change
105
+
106
+ - Remove `wait_until_listening` as adapters doesn't need to run inside threads anymore ([@Maher4Ever][])
107
+
108
+ ## 0.3.3 - March 6, 2012
109
+
110
+ ### Improvement
111
+
112
+ - Improve pause/unpause. ([@thibaudgg][])
113
+
114
+ ## 0.3.2 - March 4, 2012
115
+
116
+ ### New feature
117
+
118
+ - Add pause/unpause listener's methods. ([@thibaudgg][])
119
+
120
+ ## 0.3.1 - February 22, 2012
121
+
122
+ ### Bug fix
123
+
124
+ - [#9](https://github.com/guard/listen/issues/9) Ignore doesn't seem to work. (reported by [@markiz][], fixed by [@thibaudgg][])
125
+
126
+ ## 0.3.0 - February 21, 2012
127
+
128
+ ### New features
129
+
130
+ - Add automatic fallback to polling if system adapter doesn't work (like a DropBox folder). ([@thibaudgg][])
131
+ - Add latency and force_polling options. ([@Maher4Ever][])
132
+
133
+ ## 0.2.0 - February 13, 2012
134
+
135
+ ### New features
136
+
137
+ - Add checksum comparaison support for detecting consecutive file modifications made during the same second. ([@thibaudgg][])
138
+ - Add rb-fchange support. ([@thibaudgg][])
139
+ - Add rb-inotify support. ([@thibaudgg][] with [@Maher4Ever][] help)
140
+ - Add rb-fsevent support. ([@thibaudgg][])
141
+ - Add non-recursive diff with multiple directories support. ([@thibaudgg][])
142
+ - Ignore .DS_Store by default. ([@thibaudgg][])
143
+
144
+ ## 0.1.0 - January 28, 2012
145
+
146
+ - First version with only a polling adapter and basic features set (ignore & filter). ([@thibaudgg][])
147
+
148
+ <!--- The following link definition list is generated by PimpMyChangelog --->
149
+ [#9]: https://github.com/guard/listen/issues/9
150
+ [#17]: https://github.com/guard/listen/issues/17
151
+ [#18]: https://github.com/guard/listen/issues/18
152
+ [#21]: https://github.com/guard/listen/issues/21
153
+ [#24]: https://github.com/guard/listen/issues/24
154
+ [#27]: https://github.com/guard/listen/issues/27
155
+ [#28]: https://github.com/guard/listen/issues/28
156
+ [#32]: https://github.com/guard/listen/issues/32
157
+ [#39]: https://github.com/guard/listen/issues/39
158
+ [@Maher4Ever]: https://github.com/Maher4Ever
159
+ [@dkubb]: https://github.com/dkubb
160
+ [@ebroder]: https://github.com/ebroder
161
+ [@akerbos]: https://github.com/akerbos
162
+ [@cobychapple]: https://github.com/cobychapple
163
+ [@daemonza]: https://github.com/daemonza
164
+ [@fny]: https://github.com/fny
165
+ [@markiz]: https://github.com/markiz
166
+ [@napcs]: https://github.com/napcs
167
+ [@netzpirat]: https://github.com/netzpirat
168
+ [@nex3]: https://github.com/nex3
169
+ [@rymai]: https://github.com/rymai
170
+ [@scottdavis]: https://github.com/scottdavis
171
+ [@textgoeshere]: https://github.com/textgoeshere
172
+ [@thibaudgg]: https://github.com/thibaudgg
173
+ [WDM]: https://github.com/Maher4Ever/wdm
data/LICENSE CHANGED
@@ -1,20 +1,20 @@
1
- Copyright (c) 2012 Thibaud Guillaume-Gentil
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1
+ Copyright (c) 2012 Thibaud Guillaume-Gentil
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -50,7 +50,7 @@ module Listen
50
50
  #
51
51
  def poll
52
52
  until @stop
53
- sleep(0.1) && next if @paused
53
+ next if @paused
54
54
 
55
55
  start = Time.now.to_f
56
56
  @callback.call(@directories.dup, :recursive => true)
@@ -1,318 +1,344 @@
1
- require 'set'
2
- require 'find'
3
- require 'digest/sha1'
4
-
5
- module Listen
6
-
7
- # The directory record stores information about
8
- # a directory and keeps track of changes to
9
- # the structure of its childs.
10
- #
11
- class DirectoryRecord
12
- attr_reader :directory, :paths, :sha1_checksums
13
-
14
- DEFAULT_IGNORED_DIRECTORIES = %w[.rbx .bundle .git .svn log tmp vendor]
15
-
16
- DEFAULT_IGNORED_EXTENSIONS = %w[.DS_Store]
17
-
18
- # Defines the used precision based on the type of mtime returned by the
19
- # system (whether its in milliseconds or just seconds)
20
- #
21
- HIGH_PRECISION_SUPPORTED = File.mtime(__FILE__).to_f.to_s[-2..-1] != '.0'
22
-
23
- # Data structure used to save meta data about a path
24
- #
25
- MetaData = Struct.new(:type, :mtime)
26
-
27
- # Class methods
28
- #
29
- class << self
30
-
31
- # Creates the ignoring patterns from the default ignored
32
- # directories and extensions. It memoizes the generated patterns
33
- # to avoid unnecessary computation.
34
- #
35
- def generate_default_ignoring_patterns
36
- @@default_ignoring_patterns ||= Array.new.tap do |default_patterns|
37
- # Add directories
38
- ignored_directories = DEFAULT_IGNORED_DIRECTORIES.map { |d| Regexp.escape(d) }
39
- default_patterns << %r{^(?:#{ignored_directories.join('|')})/}
40
-
41
- # Add extensions
42
- ignored_extensions = DEFAULT_IGNORED_EXTENSIONS.map { |e| Regexp.escape(e) }
43
- default_patterns << %r{(?:#{ignored_extensions.join('|')})$}
44
- end
45
- end
46
- end
47
-
48
- # Initializes a directory record.
49
- #
50
- # @option [String] directory the directory to keep track of
51
- #
52
- def initialize(directory)
53
- raise ArgumentError, "The path '#{directory}' is not a directory!" unless File.directory?(directory)
54
-
55
- @directory = directory
56
- @ignoring_patterns = Set.new
57
- @filtering_patterns = Set.new
58
- @sha1_checksums = Hash.new
59
-
60
- @ignoring_patterns.merge(DirectoryRecord.generate_default_ignoring_patterns)
61
- end
62
-
63
- # Returns the ignoring patterns in the record
64
- #
65
- # @return [Array<Regexp>] the ignoring patterns
66
- #
67
- def ignoring_patterns
68
- @ignoring_patterns.to_a
69
- end
70
-
71
- # Returns the filtering patterns used in the record to know
72
- # which paths should be stored.
73
- #
74
- # @return [Array<Regexp>] the filtering patterns
75
- #
76
- def filtering_patterns
77
- @filtering_patterns.to_a
78
- end
79
-
80
- # Adds ignoring patterns to the record.
81
- #
82
- # @example Ignore some paths
83
- # ignore %r{^ignored/path/}, /man/
84
- #
85
- # @param [Regexp] regexp a pattern for ignoring paths
86
- #
87
- def ignore(*regexps)
88
- @ignoring_patterns.merge(regexps)
89
- end
90
-
91
- # Adds filtering patterns to the listener.
92
- #
93
- # @example Filter some files
94
- # ignore /\.txt$/, /.*\.zip/
95
- #
96
- # @param [Regexp] regexp a pattern for filtering paths
97
- #
98
- def filter(*regexps)
99
- @filtering_patterns.merge(regexps)
100
- end
101
-
102
- # Returns whether a path should be ignored or not.
103
- #
104
- # @param [String] path the path to test.
105
- #
106
- # @return [Boolean]
107
- #
108
- def ignored?(path)
109
- path = relative_to_base(path)
110
- @ignoring_patterns.any? { |pattern| pattern =~ path }
111
- end
112
-
113
- # Returns whether a path should be filtered or not.
114
- #
115
- # @param [String] path the path to test.
116
- #
117
- # @return [Boolean]
118
- #
119
- def filtered?(path)
120
- # When no filtering patterns are set, ALL files are stored.
121
- return true if @filtering_patterns.empty?
122
-
123
- path = relative_to_base(path)
124
- @filtering_patterns.any? { |pattern| pattern =~ path }
125
- end
126
-
127
- # Finds the paths that should be stored and adds them
128
- # to the paths' hash.
129
- #
130
- def build
131
- @paths = Hash.new { |h, k| h[k] = Hash.new }
132
- important_paths { |path| insert_path(path) }
133
- end
134
-
135
- # Detects changes in the passed directories, updates
136
- # the record with the new changes and returns the changes
137
- #
138
- # @param [Array] directories the list of directories scan for changes
139
- # @param [Hash] options
140
- # @option options [Boolean] recursive scan all sub-directories recursively
141
- # @option options [Boolean] relative_paths whether or not to use relative paths for changes
142
- #
143
- # @return [Hash<Array>] the changes
144
- #
145
- def fetch_changes(directories, options = {})
146
- @changes = { :modified => [], :added => [], :removed => [] }
147
- directories = directories.sort_by { |el| el.length }.reverse # diff sub-dir first
148
-
149
- directories.each do |directory|
150
- next unless directory[@directory] # Path is or inside directory
151
- detect_modifications_and_removals(directory, options)
152
- detect_additions(directory, options)
153
- end
154
-
155
- @changes
156
- end
157
-
158
- # Converts an absolute path to a path that's relative to the base directory.
159
- #
160
- # @param [String] path the path to convert
161
- #
162
- # @return [String] the relative path
163
- #
164
- def relative_to_base(path)
165
- return nil unless path[@directory]
166
- path.sub(%r{^#{Regexp.quote(@directory)}#{File::SEPARATOR}?}, '')
167
- end
168
-
169
- private
170
-
171
- # Detects modifications and removals recursively in a directory.
172
- #
173
- # @note Modifications detection begins by checking the modification time (mtime)
174
- # of files and then by checking content changes (using SHA1-checksum)
175
- # when the mtime of files is not changed.
176
- #
177
- # @param [String] directory the path to analyze
178
- # @param [Hash] options
179
- # @option options [Boolean] recursive scan all sub-directories recursively
180
- # @option options [Boolean] relative_paths whether or not to use relative paths for changes
181
- #
182
- def detect_modifications_and_removals(directory, options = {})
183
- @paths[directory].each do |basename, meta_data|
184
- path = File.join(directory, basename)
185
-
186
- case meta_data.type
187
- when 'Dir'
188
- if File.directory?(path)
189
- detect_modifications_and_removals(path, options) if options[:recursive]
190
- else
191
- detect_modifications_and_removals(path, { :recursive => true }.merge(options))
192
- @paths[directory].delete(basename)
193
- @paths.delete("#{directory}/#{basename}")
194
- end
195
- when 'File'
196
- if File.exist?(path)
197
- new_mtime = mtime_of(path)
198
-
199
- # First check if we are in the same second (to update checksums)
200
- # before checking the time difference
201
- if (meta_data.mtime.to_i == new_mtime.to_i && content_modified?(path)) || meta_data.mtime < new_mtime
202
- # Update the meta data of the files
203
- meta_data.mtime = new_mtime
204
- @paths[directory][basename] = meta_data
205
-
206
- @changes[:modified] << (options[:relative_paths] ? relative_to_base(path) : path)
207
- end
208
- else
209
- @paths[directory].delete(basename)
210
- @sha1_checksums.delete(path)
211
- @changes[:removed] << (options[:relative_paths] ? relative_to_base(path) : path)
212
- end
213
- end
214
- end
215
- end
216
-
217
- # Detects additions in a directory.
218
- #
219
- # @param [String] directory the path to analyze
220
- # @param [Hash] options
221
- # @option options [Boolean] recursive scan all sub-directories recursively
222
- # @option options [Boolean] relative_paths whether or not to use relative paths for changes
223
- #
224
- def detect_additions(directory, options = {})
225
- # Don't process removed directories
226
- return unless File.exist?(directory)
227
-
228
- Find.find(directory) do |path|
229
- next if path == @directory
230
-
231
- if File.directory?(path)
232
- # Add a trailing slash to directories when checking if a directory is
233
- # ignored to optimize finding them as Find.find doesn't.
234
- if ignored?(path + File::SEPARATOR) || (directory != path && (!options[:recursive] && existing_path?(path)))
235
- Find.prune # Don't look any further into this directory.
236
- else
237
- insert_path(path)
238
- end
239
- elsif !ignored?(path) && filtered?(path) && !existing_path?(path)
240
- if File.file?(path)
241
- @changes[:added] << (options[:relative_paths] ? relative_to_base(path) : path)
242
- insert_path(path)
243
- end
244
- end
245
- end
246
- end
247
-
248
- # Returns whether or not a file's content has been modified by
249
- # comparing the SHA1-checksum to a stored one.
250
- #
251
- # @param [String] path the file path
252
- #
253
- def content_modified?(path)
254
- sha1_checksum = Digest::SHA1.file(path).to_s
255
- return false if @sha1_checksums[path] == sha1_checksum
256
- @sha1_checksums.key?(path)
257
- rescue Errno::EACCES, Errno::ENOENT
258
- false
259
- ensure
260
- @sha1_checksums[path] = sha1_checksum if sha1_checksum
261
- end
262
-
263
- # Traverses the base directory looking for paths that should
264
- # be stored; thus paths that are filters or not ignored.
265
- #
266
- # @yield [path] an important path
267
- #
268
- def important_paths
269
- Find.find(@directory) do |path|
270
- next if path == @directory
271
-
272
- if File.directory?(path)
273
- # Add a trailing slash to directories when checking if a directory is
274
- # ignored to optimize finding them as Find.find doesn't.
275
- if ignored?(path + File::SEPARATOR)
276
- Find.prune # Don't look any further into this directory.
277
- else
278
- yield(path)
279
- end
280
- elsif !ignored?(path) && filtered?(path)
281
- yield(path)
282
- end
283
- end
284
- end
285
-
286
- # Inserts a path with its type (Dir or File) in paths hash.
287
- #
288
- # @param [String] path the path to insert in @paths.
289
- #
290
- def insert_path(path)
291
- meta_data = MetaData.new
292
- meta_data.type = File.directory?(path) ? 'Dir' : 'File'
293
- meta_data.mtime = mtime_of(path) unless meta_data.type == 'Dir' # mtimes of dirs are not used yet
294
- @paths[File.dirname(path)][File.basename(path)] = meta_data
295
- rescue Errno::ENOENT
296
- end
297
-
298
- # Returns whether or not a path exists in the paths hash.
299
- #
300
- # @param [String] path the path to check
301
- #
302
- # @return [Boolean]
303
- #
304
- def existing_path?(path)
305
- @paths[File.dirname(path)][File.basename(path)] != nil
306
- end
307
-
308
- # Returns the modification time of a file based on the precision defined by the system
309
- #
310
- # @param [String] file the file for which the mtime must be returned
311
- #
312
- # @return [Fixnum, Float] the mtime of the file
313
- #
314
- def mtime_of(file)
315
- File.lstat(file).mtime.send(HIGH_PRECISION_SUPPORTED ? :to_f : :to_i)
316
- end
317
- end
318
- end
1
+ require 'set'
2
+ require 'find'
3
+ require 'digest/sha1'
4
+
5
+ module Listen
6
+
7
+ # The directory record stores information about
8
+ # a directory and keeps track of changes to
9
+ # the structure of its childs.
10
+ #
11
+ class DirectoryRecord
12
+ attr_reader :directory, :paths, :sha1_checksums
13
+
14
+ DEFAULT_IGNORED_DIRECTORIES = %w[.rbx .bundle .git .svn log tmp vendor]
15
+
16
+ DEFAULT_IGNORED_EXTENSIONS = %w[.DS_Store]
17
+
18
+ # Defines the used precision based on the type of mtime returned by the
19
+ # system (whether its in milliseconds or just seconds)
20
+ #
21
+ HIGH_PRECISION_SUPPORTED = File.mtime(__FILE__).to_f.to_s[-2..-1] != '.0'
22
+
23
+ # Data structure used to save meta data about a path
24
+ #
25
+ MetaData = Struct.new(:type, :mtime)
26
+
27
+ # Class methods
28
+ #
29
+ class << self
30
+
31
+ # Creates the ignoring patterns from the default ignored
32
+ # directories and extensions. It memoizes the generated patterns
33
+ # to avoid unnecessary computation.
34
+ #
35
+ def generate_default_ignoring_patterns
36
+ @@default_ignoring_patterns ||= Array.new.tap do |default_patterns|
37
+ # Add directories
38
+ ignored_directories = DEFAULT_IGNORED_DIRECTORIES.map { |d| Regexp.escape(d) }
39
+ default_patterns << %r{^(?:#{ignored_directories.join('|')})/}
40
+
41
+ # Add extensions
42
+ ignored_extensions = DEFAULT_IGNORED_EXTENSIONS.map { |e| Regexp.escape(e) }
43
+ default_patterns << %r{(?:#{ignored_extensions.join('|')})$}
44
+ end
45
+ end
46
+ end
47
+
48
+ # Initializes a directory record.
49
+ #
50
+ # @option [String] directory the directory to keep track of
51
+ #
52
+ def initialize(directory)
53
+ raise ArgumentError, "The path '#{directory}' is not a directory!" unless File.directory?(directory)
54
+
55
+ @directory = directory
56
+ @ignoring_patterns = Set.new
57
+ @filtering_patterns = Set.new
58
+ @sha1_checksums = Hash.new
59
+
60
+ @ignoring_patterns.merge(DirectoryRecord.generate_default_ignoring_patterns)
61
+ end
62
+
63
+ # Returns the ignoring patterns in the record
64
+ #
65
+ # @return [Array<Regexp>] the ignoring patterns
66
+ #
67
+ def ignoring_patterns
68
+ @ignoring_patterns.to_a
69
+ end
70
+
71
+ # Returns the filtering patterns used in the record to know
72
+ # which paths should be stored.
73
+ #
74
+ # @return [Array<Regexp>] the filtering patterns
75
+ #
76
+ def filtering_patterns
77
+ @filtering_patterns.to_a
78
+ end
79
+
80
+ # Adds ignoring patterns to the record.
81
+ #
82
+ # @example Ignore some paths
83
+ # ignore %r{^ignored/path/}, /man/
84
+ #
85
+ # @param [Regexp] regexp a pattern for ignoring paths
86
+ #
87
+ def ignore(*regexps)
88
+ @ignoring_patterns.merge(regexps)
89
+ end
90
+
91
+ # Adds filtering patterns to the listener.
92
+ #
93
+ # @example Filter some files
94
+ # ignore /\.txt$/, /.*\.zip/
95
+ #
96
+ # @param [Regexp] regexp a pattern for filtering paths
97
+ #
98
+ def filter(*regexps)
99
+ @filtering_patterns.merge(regexps)
100
+ end
101
+
102
+ # Returns whether a path should be ignored or not.
103
+ #
104
+ # @param [String] path the path to test.
105
+ #
106
+ # @return [Boolean]
107
+ #
108
+ def ignored?(path)
109
+ path = relative_to_base(path)
110
+ @ignoring_patterns.any? { |pattern| pattern =~ path }
111
+ end
112
+
113
+ # Returns whether a path should be filtered or not.
114
+ #
115
+ # @param [String] path the path to test.
116
+ #
117
+ # @return [Boolean]
118
+ #
119
+ def filtered?(path)
120
+ # When no filtering patterns are set, ALL files are stored.
121
+ return true if @filtering_patterns.empty?
122
+
123
+ path = relative_to_base(path)
124
+ @filtering_patterns.any? { |pattern| pattern =~ path }
125
+ end
126
+
127
+ # Finds the paths that should be stored and adds them
128
+ # to the paths' hash.
129
+ #
130
+ def build
131
+ @paths = Hash.new { |h, k| h[k] = Hash.new }
132
+ important_paths { |path| insert_path(path) }
133
+ end
134
+
135
+ # Detects changes in the passed directories, updates
136
+ # the record with the new changes and returns the changes
137
+ #
138
+ # @param [Array] directories the list of directories scan for changes
139
+ # @param [Hash] options
140
+ # @option options [Boolean] recursive scan all sub-directories recursively
141
+ # @option options [Boolean] relative_paths whether or not to use relative paths for changes
142
+ #
143
+ # @return [Hash<Array>] the changes
144
+ #
145
+ def fetch_changes(directories, options = {})
146
+ @changes = { :modified => [], :added => [], :removed => [] }
147
+ directories = directories.sort_by { |el| el.length }.reverse # diff sub-dir first
148
+
149
+ directories.each do |directory|
150
+ next unless directory[@directory] # Path is or inside directory
151
+ detect_modifications_and_removals(directory, options)
152
+ detect_additions(directory, options)
153
+ end
154
+
155
+ @changes
156
+ end
157
+
158
+ # Converts an absolute path to a path that's relative to the base directory.
159
+ #
160
+ # @param [String] path the path to convert
161
+ #
162
+ # @return [String] the relative path
163
+ #
164
+ def relative_to_base(path)
165
+ return nil unless path[@directory]
166
+ path.sub(%r{^#{Regexp.quote(@directory)}#{File::SEPARATOR}?}, '')
167
+ end
168
+
169
+ private
170
+
171
+ # Detects modifications and removals recursively in a directory.
172
+ #
173
+ # @note Modifications detection begins by checking the modification time (mtime)
174
+ # of files and then by checking content changes (using SHA1-checksum)
175
+ # when the mtime of files is not changed.
176
+ #
177
+ # @param [String] directory the path to analyze
178
+ # @param [Hash] options
179
+ # @option options [Boolean] recursive scan all sub-directories recursively
180
+ # @option options [Boolean] relative_paths whether or not to use relative paths for changes
181
+ #
182
+ def detect_modifications_and_removals(directory, options = {})
183
+ @paths[directory].each do |basename, meta_data|
184
+ path = File.join(directory, basename)
185
+
186
+ case meta_data.type
187
+ when 'Dir'
188
+ if File.directory?(path)
189
+ detect_modifications_and_removals(path, options) if options[:recursive]
190
+ else
191
+ detect_modifications_and_removals(path, { :recursive => true }.merge(options))
192
+ @paths[directory].delete(basename)
193
+ @paths.delete("#{directory}/#{basename}")
194
+ end
195
+ when 'File'
196
+ if File.exist?(path)
197
+ new_mtime = mtime_of(path)
198
+
199
+ # First check if we are in the same second (to update checksums)
200
+ # before checking the time difference
201
+ if (meta_data.mtime.to_i == new_mtime.to_i && content_modified?(path)) || meta_data.mtime < new_mtime
202
+ # Update the sha1 checksum of the file
203
+ insert_sha1_checksum(path)
204
+
205
+ # Update the meta data of the file
206
+ meta_data.mtime = new_mtime
207
+ @paths[directory][basename] = meta_data
208
+
209
+ @changes[:modified] << (options[:relative_paths] ? relative_to_base(path) : path)
210
+ end
211
+ else
212
+ @paths[directory].delete(basename)
213
+ @sha1_checksums.delete(path)
214
+ @changes[:removed] << (options[:relative_paths] ? relative_to_base(path) : path)
215
+ end
216
+ end
217
+ end
218
+ end
219
+
220
+ # Detects additions in a directory.
221
+ #
222
+ # @param [String] directory the path to analyze
223
+ # @param [Hash] options
224
+ # @option options [Boolean] recursive scan all sub-directories recursively
225
+ # @option options [Boolean] relative_paths whether or not to use relative paths for changes
226
+ #
227
+ def detect_additions(directory, options = {})
228
+ # Don't process removed directories
229
+ return unless File.exist?(directory)
230
+
231
+ Find.find(directory) do |path|
232
+ next if path == @directory
233
+
234
+ if File.directory?(path)
235
+ # Add a trailing slash to directories when checking if a directory is
236
+ # ignored to optimize finding them as Find.find doesn't.
237
+ if ignored?(path + File::SEPARATOR) || (directory != 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 !ignored?(path) && filtered?(path) && !existing_path?(path)
243
+ if File.file?(path)
244
+ @changes[:added] << (options[:relative_paths] ? relative_to_base(path) : path)
245
+ insert_path(path)
246
+ end
247
+ end
248
+ end
249
+ end
250
+
251
+ # Returns whether or not a file's content has been modified by
252
+ # comparing the SHA1-checksum to a stored one.
253
+ # Ensure that the SHA1-checksum is inserted to the sha1_checksums
254
+ # array for later comparaison if false.
255
+ #
256
+ # @param [String] path the file path
257
+ #
258
+ def content_modified?(path)
259
+ @sha1_checksum = sha1_checksum(path)
260
+ if @sha1_checksums[path] == @sha1_checksum || !@sha1_checksums.key?(path)
261
+ insert_sha1_checksum(path)
262
+ false
263
+ else
264
+ true
265
+ end
266
+ end
267
+
268
+ # Inserts a SHA1-checksum path in @SHA1-checksums hash.
269
+ #
270
+ # @param [String] path the SHA1-checksum path to insert in @sha1_checksums.
271
+ #
272
+ def insert_sha1_checksum(path)
273
+ if @sha1_checksum ||= sha1_checksum(path)
274
+ @sha1_checksums[path] = @sha1_checksum
275
+ @sha1_checksum = nil
276
+ end
277
+ end
278
+
279
+ # Returns the SHA1-checksum for the file path.
280
+ #
281
+ # @param [String] path the file path
282
+ #
283
+ def sha1_checksum(path)
284
+ Digest::SHA1.file(path).to_s
285
+ rescue Errno::EACCES, Errno::ENOENT
286
+ nil
287
+ end
288
+
289
+ # Traverses the base directory looking for paths that should
290
+ # be stored; thus paths that are filters or not ignored.
291
+ #
292
+ # @yield [path] an important path
293
+ #
294
+ def important_paths
295
+ Find.find(@directory) do |path|
296
+ next if path == @directory
297
+
298
+ if File.directory?(path)
299
+ # Add a trailing slash to directories when checking if a directory is
300
+ # ignored to optimize finding them as Find.find doesn't.
301
+ if ignored?(path + File::SEPARATOR)
302
+ Find.prune # Don't look any further into this directory.
303
+ else
304
+ yield(path)
305
+ end
306
+ elsif !ignored?(path) && filtered?(path)
307
+ yield(path)
308
+ end
309
+ end
310
+ end
311
+
312
+ # Inserts a path with its type (Dir or File) in paths hash.
313
+ #
314
+ # @param [String] path the path to insert in @paths.
315
+ #
316
+ def insert_path(path)
317
+ meta_data = MetaData.new
318
+ meta_data.type = File.directory?(path) ? 'Dir' : 'File'
319
+ meta_data.mtime = mtime_of(path) unless meta_data.type == 'Dir' # mtimes of dirs are not used yet
320
+ @paths[File.dirname(path)][File.basename(path)] = meta_data
321
+ rescue Errno::ENOENT
322
+ end
323
+
324
+ # Returns whether or not a path exists in the paths hash.
325
+ #
326
+ # @param [String] path the path to check
327
+ #
328
+ # @return [Boolean]
329
+ #
330
+ def existing_path?(path)
331
+ @paths[File.dirname(path)][File.basename(path)] != nil
332
+ end
333
+
334
+ # Returns the modification time of a file based on the precision defined by the system
335
+ #
336
+ # @param [String] file the file for which the mtime must be returned
337
+ #
338
+ # @return [Fixnum, Float] the mtime of the file
339
+ #
340
+ def mtime_of(file)
341
+ File.lstat(file).mtime.send(HIGH_PRECISION_SUPPORTED ? :to_f : :to_i)
342
+ end
343
+ end
344
+ end