joshbuddy-guard 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/CHANGELOG.md +370 -0
  2. data/LICENSE +20 -0
  3. data/README.md +470 -0
  4. data/bin/fsevent_watch_guard +0 -0
  5. data/bin/guard +6 -0
  6. data/images/failed.png +0 -0
  7. data/images/pending.png +0 -0
  8. data/images/success.png +0 -0
  9. data/lib/guard.rb +463 -0
  10. data/lib/guard/cli.rb +125 -0
  11. data/lib/guard/dsl.rb +370 -0
  12. data/lib/guard/dsl_describer.rb +150 -0
  13. data/lib/guard/group.rb +37 -0
  14. data/lib/guard/guard.rb +129 -0
  15. data/lib/guard/hook.rb +118 -0
  16. data/lib/guard/interactor.rb +116 -0
  17. data/lib/guard/listener.rb +351 -0
  18. data/lib/guard/listeners/darwin.rb +60 -0
  19. data/lib/guard/listeners/linux.rb +91 -0
  20. data/lib/guard/listeners/polling.rb +55 -0
  21. data/lib/guard/listeners/windows.rb +61 -0
  22. data/lib/guard/notifier.rb +290 -0
  23. data/lib/guard/templates/Guardfile +2 -0
  24. data/lib/guard/ui.rb +193 -0
  25. data/lib/guard/version.rb +6 -0
  26. data/lib/guard/watcher.rb +114 -0
  27. data/lib/vendor/darwin/Gemfile +6 -0
  28. data/lib/vendor/darwin/Guardfile +8 -0
  29. data/lib/vendor/darwin/LICENSE +20 -0
  30. data/lib/vendor/darwin/README.rdoc +254 -0
  31. data/lib/vendor/darwin/Rakefile +21 -0
  32. data/lib/vendor/darwin/ext/extconf.rb +61 -0
  33. data/lib/vendor/darwin/ext/fsevent/fsevent_watch.c +226 -0
  34. data/lib/vendor/darwin/lib/rb-fsevent.rb +2 -0
  35. data/lib/vendor/darwin/lib/rb-fsevent/fsevent.rb +105 -0
  36. data/lib/vendor/darwin/lib/rb-fsevent/version.rb +3 -0
  37. data/lib/vendor/darwin/rb-fsevent.gemspec +24 -0
  38. data/lib/vendor/darwin/spec/fixtures/folder1/file1.txt +0 -0
  39. data/lib/vendor/darwin/spec/fixtures/folder1/folder2/file2.txt +0 -0
  40. data/lib/vendor/darwin/spec/rb-fsevent/fsevent_spec.rb +75 -0
  41. data/lib/vendor/darwin/spec/spec_helper.rb +24 -0
  42. data/lib/vendor/linux/MIT-LICENSE +20 -0
  43. data/lib/vendor/linux/README.md +66 -0
  44. data/lib/vendor/linux/Rakefile +54 -0
  45. data/lib/vendor/linux/VERSION +1 -0
  46. data/lib/vendor/linux/lib/rb-inotify.rb +17 -0
  47. data/lib/vendor/linux/lib/rb-inotify/event.rb +139 -0
  48. data/lib/vendor/linux/lib/rb-inotify/native.rb +31 -0
  49. data/lib/vendor/linux/lib/rb-inotify/native/flags.rb +89 -0
  50. data/lib/vendor/linux/lib/rb-inotify/notifier.rb +308 -0
  51. data/lib/vendor/linux/lib/rb-inotify/watcher.rb +83 -0
  52. data/lib/vendor/linux/rb-inotify.gemspec +53 -0
  53. data/lib/vendor/windows/Gemfile +4 -0
  54. data/lib/vendor/windows/README.md +34 -0
  55. data/lib/vendor/windows/Rakefile +18 -0
  56. data/lib/vendor/windows/lib/rb-fchange.rb +14 -0
  57. data/lib/vendor/windows/lib/rb-fchange/event.rb +29 -0
  58. data/lib/vendor/windows/lib/rb-fchange/native.rb +45 -0
  59. data/lib/vendor/windows/lib/rb-fchange/native/flags.rb +78 -0
  60. data/lib/vendor/windows/lib/rb-fchange/notifier.rb +149 -0
  61. data/lib/vendor/windows/lib/rb-fchange/version.rb +3 -0
  62. data/lib/vendor/windows/lib/rb-fchange/watcher.rb +99 -0
  63. data/lib/vendor/windows/rb-fchange.gemspec +34 -0
  64. data/lib/vendor/windows/spec/fixtures/folder1/file1.txt +0 -0
  65. data/lib/vendor/windows/spec/fixtures/folder1/folder2/file2.txt +0 -0
  66. data/lib/vendor/windows/spec/rb-fchange/fchange_spec.rb +119 -0
  67. data/lib/vendor/windows/spec/spec_helper.rb +21 -0
  68. data/man/guard.1 +96 -0
  69. data/man/guard.1.html +181 -0
  70. metadata +193 -0
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Nathan Weizenbaum
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.
@@ -0,0 +1,66 @@
1
+ # rb-inotify
2
+
3
+ This is a simple wrapper over the [inotify](http://en.wikipedia.org/wiki/Inotify) Linux kernel subsystem
4
+ for monitoring changes to files and directories.
5
+ It uses the [FFI](http://wiki.github.com/ffi/ffi) gem to avoid having to compile a C extension.
6
+
7
+ [API documentation is available on rdoc.info](http://rdoc.info/projects/nex3/rb-inotify).
8
+
9
+ ## Basic Usage
10
+
11
+ The API is similar to the inotify C API, but with a more Rubyish feel.
12
+ First, create a notifier:
13
+
14
+ notifier = INotify::Notifier.new
15
+
16
+ Then, tell it to watch the paths you're interested in
17
+ for the events you care about:
18
+
19
+ notifier.watch("path/to/foo.txt", :modify) {puts "foo.txt was modified!"}
20
+ notifier.watch("path/to/bar", :moved_to, :create) do |event|
21
+ puts "#{event.name} is now in path/to/bar!"
22
+ end
23
+
24
+ Inotify can watch directories or individual files.
25
+ It can pay attention to all sorts of events;
26
+ for a full list, see [the inotify man page](http://www.tin.org/bin/man.cgi?section=7&topic=inotify).
27
+
28
+ Finally, you get at the events themselves:
29
+
30
+ notifier.run
31
+
32
+ This will loop infinitely, calling the appropriate callbacks when the files are changed.
33
+ If you don't want infinite looping,
34
+ you can also block until there are available events,
35
+ process them all at once,
36
+ and then continue on your merry way:
37
+
38
+ notifier.process
39
+
40
+ ## Advanced Usage
41
+
42
+ Sometimes it's necessary to have finer control over the underlying IO operations
43
+ than is provided by the simple callback API.
44
+ The trick to this is that the \{INotify::Notifier#to_io Notifier#to_io} method
45
+ returns a fully-functional IO object,
46
+ with a file descriptor and everything.
47
+ This means, for example, that it can be passed to `IO#select`:
48
+
49
+ # Wait 10 seconds for an event then give up
50
+ if IO.select([notifier.to_io], [], [], 10)
51
+ notifier.process
52
+ end
53
+
54
+ It can even be used with EventMachine:
55
+
56
+ require 'eventmachine'
57
+
58
+ EM.run do
59
+ EM.watch notifier.to_io do
60
+ notifier.process
61
+ end
62
+ end
63
+
64
+ Unfortunately, this currently doesn't work under JRuby.
65
+ JRuby currently doesn't use native file descriptors for the IO object,
66
+ so we can't use the notifier's file descriptor as a stand-in.
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "rb-inotify"
8
+ gem.summary = "A Ruby wrapper for Linux's inotify, using FFI"
9
+ gem.description = gem.summary
10
+ gem.email = "nex342@gmail.com"
11
+ gem.homepage = "http://github.com/nex3/rb-inotify"
12
+ gem.authors = ["Nathan Weizenbaum"]
13
+ gem.add_dependency "ffi", ">= 0.5.0"
14
+ gem.add_development_dependency "yard", ">= 0.4.0"
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
19
+ end
20
+
21
+ task(:permissions) {sh %{chmod -R a+r .}}
22
+ Rake::Task[:build].prerequisites.unshift('permissions')
23
+
24
+ module Jeweler::VersionHelper::PlaintextExtension
25
+ def write_with_inotify
26
+ write_without_inotify
27
+ filename = File.join(File.dirname(__FILE__), "lib/rb-inotify.rb")
28
+ text = File.read(filename)
29
+ File.open(filename, 'w') do |f|
30
+ f.write text.gsub(/^( VERSION = ).*/, '\1' + [major, minor, patch].inspect)
31
+ end
32
+ end
33
+ alias_method :write_without_inotify, :write
34
+ alias_method :write, :write_with_inotify
35
+ end
36
+
37
+ class Jeweler::Commands::Version::Base
38
+ def commit_version_with_inotify
39
+ return unless self.repo
40
+ self.repo.add(File.join(File.dirname(__FILE__), "lib/rb-inotify.rb"))
41
+ commit_version_without_inotify
42
+ end
43
+ alias_method :commit_version_without_inotify, :commit_version
44
+ alias_method :commit_version, :commit_version_with_inotify
45
+ end
46
+
47
+ begin
48
+ require 'yard'
49
+ YARD::Rake::YardocTask.new
50
+ rescue LoadError
51
+ task :yardoc do
52
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
53
+ end
54
+ end
@@ -0,0 +1 @@
1
+ 0.8.8
@@ -0,0 +1,17 @@
1
+ require 'rb-inotify/native'
2
+ require 'rb-inotify/native/flags'
3
+ require 'rb-inotify/notifier'
4
+ require 'rb-inotify/watcher'
5
+ require 'rb-inotify/event'
6
+
7
+ # The root module of the library, which is laid out as so:
8
+ #
9
+ # * {Notifier} -- The main class, where the notifications are set up
10
+ # * {Watcher} -- A watcher for a single file or directory
11
+ # * {Event} -- An filesystem event notification
12
+ module INotify
13
+ # An array containing the version number of rb-inotify.
14
+ # The numbers in the array are the major, minor, and patch versions,
15
+ # respectively.
16
+ VERSION = [0, 8, 8]
17
+ end
@@ -0,0 +1,139 @@
1
+ module INotify
2
+ # An event caused by a change on the filesystem.
3
+ # Each {Watcher} can fire many events,
4
+ # which are passed to that watcher's callback.
5
+ class Event
6
+ # A list of other events that are related to this one.
7
+ # Currently, this is only used for files that are moved within the same directory:
8
+ # the `:moved_from` and the `:moved_to` events will be related.
9
+ #
10
+ # @return [Array<Event>]
11
+ attr_reader :related
12
+
13
+ # The name of the file that the event occurred on.
14
+ # This is only set for events that occur on files in directories;
15
+ # otherwise, it's `""`.
16
+ # Similarly, if the event is being fired for the directory itself
17
+ # the name will be `""`
18
+ #
19
+ # This pathname is relative to the enclosing directory.
20
+ # For the absolute pathname, use \{#absolute\_name}.
21
+ # Note that when the `:recursive` flag is passed to {Notifier#watch},
22
+ # events in nested subdirectories will still have a `#name` field
23
+ # relative to their immediately enclosing directory.
24
+ # For example, an event on the file `"foo/bar/baz"`
25
+ # will have name `"baz"`.
26
+ #
27
+ # @return [String]
28
+ attr_reader :name
29
+
30
+ # The {Notifier} that fired this event.
31
+ #
32
+ # @return [Notifier]
33
+ attr_reader :notifier
34
+
35
+ # An integer specifying that this event is related to some other event,
36
+ # which will have the same cookie.
37
+ #
38
+ # Currently, this is only used for files that are moved within the same directory.
39
+ # Both the `:moved_from` and the `:moved_to` events will have the same cookie.
40
+ #
41
+ # @private
42
+ # @return [Fixnum]
43
+ attr_reader :cookie
44
+
45
+ # The {Watcher#id id} of the {Watcher} that fired this event.
46
+ #
47
+ # @private
48
+ # @return [Fixnum]
49
+ attr_reader :watcher_id
50
+
51
+ # Returns the {Watcher} that fired this event.
52
+ #
53
+ # @return [Watcher]
54
+ def watcher
55
+ @watcher ||= @notifier.watchers[@watcher_id]
56
+ end
57
+
58
+ # The absolute path of the file that the event occurred on.
59
+ #
60
+ # This is actually only as absolute as the path passed to the {Watcher}
61
+ # that created this event.
62
+ # However, it is relative to the working directory,
63
+ # assuming that hasn't changed since the watcher started.
64
+ #
65
+ # @return [String]
66
+ def absolute_name
67
+ return watcher.path if name.empty?
68
+ return File.join(watcher.path, name)
69
+ end
70
+
71
+ # Returns the flags that describe this event.
72
+ # This is generally similar to the input to {Notifier#watch},
73
+ # except that it won't contain options flags nor `:all_events`,
74
+ # and it may contain one or more of the following flags:
75
+ #
76
+ # `:unmount`
77
+ # : The filesystem containing the watched file or directory was unmounted.
78
+ #
79
+ # `:ignored`
80
+ # : The \{#watcher watcher} was closed, or the watched file or directory was deleted.
81
+ #
82
+ # `:isdir`
83
+ # : The subject of this event is a directory.
84
+ #
85
+ # @return [Array<Symbol>]
86
+ def flags
87
+ @flags ||= Native::Flags.from_mask(@native[:mask])
88
+ end
89
+
90
+ # Constructs an {Event} object from a string of binary data,
91
+ # and destructively modifies the string to get rid of the initial segment
92
+ # used to construct the Event.
93
+ #
94
+ # @private
95
+ # @param data [String] The string to be modified
96
+ # @param notifier [Notifier] The {Notifier} that fired the event
97
+ # @return [Event, nil] The event, or `nil` if the string is empty
98
+ def self.consume(data, notifier)
99
+ return nil if data.empty?
100
+ ev = new(data, notifier)
101
+ data.replace data[ev.size..-1]
102
+ ev
103
+ end
104
+
105
+ # Creates an event from a string of binary data.
106
+ # Differs from {Event.consume} in that it doesn't modify the string.
107
+ #
108
+ # @private
109
+ # @param data [String] The data string
110
+ # @param notifier [Notifier] The {Notifier} that fired the event
111
+ def initialize(data, notifier)
112
+ ptr = FFI::MemoryPointer.from_string(data)
113
+ @native = Native::Event.new(ptr)
114
+ @related = []
115
+ @cookie = @native[:cookie]
116
+ @name = data[@native.size, @native[:len]].gsub(/\0+$/, '')
117
+ @notifier = notifier
118
+ @watcher_id = @native[:wd]
119
+
120
+ raise Exception.new("inotify event queue has overflowed.") if @native[:mask] & Native::Flags::IN_Q_OVERFLOW != 0
121
+ end
122
+
123
+ # Calls the callback of the watcher that fired this event,
124
+ # passing in the event itself.
125
+ #
126
+ # @private
127
+ def callback!
128
+ watcher.callback!(self)
129
+ end
130
+
131
+ # Returns the size of this event object in bytes,
132
+ # including the \{#name} string.
133
+ #
134
+ # @return [Fixnum]
135
+ def size
136
+ @native.size + @native[:len]
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,31 @@
1
+ require 'ffi'
2
+
3
+ module INotify
4
+ # This module contains the low-level foreign-function interface code
5
+ # for dealing with the inotify C APIs.
6
+ # It's an implementation detail, and not meant for users to deal with.
7
+ #
8
+ # @private
9
+ module Native
10
+ extend FFI::Library
11
+ ffi_lib "c"
12
+
13
+ # The C struct describing an inotify event.
14
+ #
15
+ # @private
16
+ class Event < FFI::Struct
17
+ layout(
18
+ :wd, :int,
19
+ :mask, :uint32,
20
+ :cookie, :uint32,
21
+ :len, :uint32)
22
+ end
23
+
24
+ attach_function :inotify_init, [], :int
25
+ attach_function :inotify_add_watch, [:int, :string, :uint32], :int
26
+ attach_function :inotify_rm_watch, [:int, :uint32], :int
27
+
28
+ attach_function :read, [:int, :pointer, :size_t], :ssize_t
29
+ attach_function :close, [:int], :int
30
+ end
31
+ end
@@ -0,0 +1,89 @@
1
+ module INotify
2
+ module Native
3
+ # A module containing all the inotify flags
4
+ # to be passed to {Notifier#watch}.
5
+ #
6
+ # @private
7
+ module Flags
8
+ # File was accessed.
9
+ IN_ACCESS = 0x00000001
10
+ # Metadata changed.
11
+ IN_ATTRIB = 0x00000004
12
+ # Writtable file was closed.
13
+ IN_CLOSE_WRITE = 0x00000008
14
+ # File was modified.
15
+ IN_MODIFY = 0x00000002
16
+ # Unwrittable file closed.
17
+ IN_CLOSE_NOWRITE = 0x00000010
18
+ # File was opened.
19
+ IN_OPEN = 0x00000020
20
+ # File was moved from X.
21
+ IN_MOVED_FROM = 0x00000040
22
+ # File was moved to Y.
23
+ IN_MOVED_TO = 0x00000080
24
+ # Subfile was created.
25
+ IN_CREATE = 0x00000100
26
+ # Subfile was deleted.
27
+ IN_DELETE = 0x00000200
28
+ # Self was deleted.
29
+ IN_DELETE_SELF = 0x00000400
30
+ # Self was moved.
31
+ IN_MOVE_SELF = 0x00000800
32
+
33
+ ## Helper events.
34
+
35
+ # Close.
36
+ IN_CLOSE = (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
37
+ # Moves.
38
+ IN_MOVE = (IN_MOVED_FROM | IN_MOVED_TO)
39
+ # All events which a program can wait on.
40
+ IN_ALL_EVENTS = (IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE |
41
+ IN_CLOSE_NOWRITE | IN_OPEN | IN_MOVED_FROM | IN_MOVED_TO | IN_CREATE |
42
+ IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF)
43
+
44
+
45
+ ## Special flags.
46
+
47
+ # Only watch the path if it is a directory.
48
+ IN_ONLYDIR = 0x01000000
49
+ # Do not follow a sym link.
50
+ IN_DONT_FOLLOW = 0x02000000
51
+ # Add to the mask of an already existing watch.
52
+ IN_MASK_ADD = 0x20000000
53
+ # Only send event once.
54
+ IN_ONESHOT = 0x80000000
55
+
56
+
57
+ ## Events sent by the kernel.
58
+
59
+ # Backing fs was unmounted.
60
+ IN_UNMOUNT = 0x00002000
61
+ # Event queued overflowed.
62
+ IN_Q_OVERFLOW = 0x00004000
63
+ # File was ignored.
64
+ IN_IGNORED = 0x00008000
65
+ # Event occurred against dir.
66
+ IN_ISDIR = 0x40000000
67
+
68
+ # Converts a list of flags to the bitmask that the C API expects.
69
+ #
70
+ # @param flags [Array<Symbol>]
71
+ # @return [Fixnum]
72
+ def self.to_mask(flags)
73
+ flags.map {|flag| const_get("IN_#{flag.to_s.upcase}")}.
74
+ inject(0) {|mask, flag| mask | flag}
75
+ end
76
+
77
+ # Converts a bitmask from the C API into a list of flags.
78
+ #
79
+ # @param mask [Fixnum]
80
+ # @return [Array<Symbol>]
81
+ def self.from_mask(mask)
82
+ constants.map {|c| c.to_s}.select do |c|
83
+ next false unless c =~ /^IN_/
84
+ const_get(c) & mask != 0
85
+ end.map {|c| c.sub("IN_", "").downcase.to_sym} - [:all_events]
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,308 @@
1
+ module INotify
2
+ # Notifier wraps a single instance of inotify.
3
+ # It's possible to have more than one instance,
4
+ # but usually unnecessary.
5
+ #
6
+ # @example
7
+ # # Create the notifier
8
+ # notifier = INotify::Notifier.new
9
+ #
10
+ # # Run this callback whenever the file path/to/foo.txt is read
11
+ # notifier.watch("path/to/foo.txt", :access) do
12
+ # puts "Foo.txt was accessed!"
13
+ # end
14
+ #
15
+ # # Watch for any file in the directory being deleted
16
+ # # or moved out of the directory.
17
+ # notifier.watch("path/to/directory", :delete, :moved_from) do |event|
18
+ # # The #name field of the event object contains the name of the affected file
19
+ # puts "#{event.name} is no longer in the directory!"
20
+ # end
21
+ #
22
+ # # Nothing happens until you run the notifier!
23
+ # notifier.run
24
+ class Notifier
25
+ # A list of directories that should never be recursively watched.
26
+ #
27
+ # * Files in `/dev/fd` sometimes register as directories, but are not enumerable.
28
+ RECURSIVE_BLACKLIST = %w[/dev/fd]
29
+
30
+ # A hash from {Watcher} ids to the instances themselves.
31
+ #
32
+ # @private
33
+ # @return [{Fixnum => Watcher}]
34
+ attr_reader :watchers
35
+
36
+ # The underlying file descriptor for this notifier.
37
+ # This is a valid OS file descriptor, and can be used as such
38
+ # (except under JRuby -- see \{#to\_io}).
39
+ #
40
+ # @return [Fixnum]
41
+ attr_reader :fd
42
+
43
+ # @return [Boolean] Whether or not this Ruby implementation supports
44
+ # wrapping the native file descriptor in a Ruby IO wrapper.
45
+ def self.supports_ruby_io?
46
+ RUBY_PLATFORM !~ /java/
47
+ end
48
+
49
+ # Creates a new {Notifier}.
50
+ #
51
+ # @return [Notifier]
52
+ # @raise [SystemCallError] if inotify failed to initialize for some reason
53
+ def initialize
54
+ @fd = Native.inotify_init
55
+ @watchers = {}
56
+ return unless @fd < 0
57
+
58
+ raise SystemCallError.new(
59
+ "Failed to initialize inotify" +
60
+ case FFI.errno
61
+ when Errno::EMFILE::Errno; ": the user limit on the total number of inotify instances has been reached."
62
+ when Errno::ENFILE::Errno; ": the system limit on the total number of file descriptors has been reached."
63
+ when Errno::ENOMEM::Errno; ": insufficient kernel memory is available."
64
+ else; ""
65
+ end,
66
+ FFI.errno)
67
+ end
68
+
69
+ # Returns a Ruby IO object wrapping the underlying file descriptor.
70
+ # Since this file descriptor is fully functional (except under JRuby),
71
+ # this IO object can be used in any way a Ruby-created IO object can.
72
+ # This includes passing it to functions like `#select`.
73
+ #
74
+ # Note that this always returns the same IO object.
75
+ # Creating lots of IO objects for the same file descriptor
76
+ # can cause some odd problems.
77
+ #
78
+ # **This is not supported under JRuby**.
79
+ # JRuby currently doesn't use native file descriptors for the IO object,
80
+ # so we can't use this file descriptor as a stand-in.
81
+ #
82
+ # @return [IO] An IO object wrapping the file descriptor
83
+ # @raise [NotImplementedError] if this is being called in JRuby
84
+ def to_io
85
+ unless self.class.supports_ruby_io?
86
+ raise NotImplementedError.new("INotify::Notifier#to_io is not supported under JRuby")
87
+ end
88
+ @io ||= IO.new(@fd)
89
+ end
90
+
91
+ # Watches a file or directory for changes,
92
+ # calling the callback when there are.
93
+ # This is only activated once \{#process} or \{#run} is called.
94
+ #
95
+ # **Note that by default, this does not recursively watch subdirectories
96
+ # of the watched directory**.
97
+ # To do so, use the `:recursive` flag.
98
+ #
99
+ # ## Flags
100
+ #
101
+ # `:access`
102
+ # : A file is accessed (that is, read).
103
+ #
104
+ # `:attrib`
105
+ # : A file's metadata is changed (e.g. permissions, timestamps, etc).
106
+ #
107
+ # `:close_write`
108
+ # : A file that was opened for writing is closed.
109
+ #
110
+ # `:close_nowrite`
111
+ # : A file that was not opened for writing is closed.
112
+ #
113
+ # `:modify`
114
+ # : A file is modified.
115
+ #
116
+ # `:open`
117
+ # : A file is opened.
118
+ #
119
+ # ### Directory-Specific Flags
120
+ #
121
+ # These flags only apply when a directory is being watched.
122
+ #
123
+ # `:moved_from`
124
+ # : A file is moved out of the watched directory.
125
+ #
126
+ # `:moved_to`
127
+ # : A file is moved into the watched directory.
128
+ #
129
+ # `:create`
130
+ # : A file is created in the watched directory.
131
+ #
132
+ # `:delete`
133
+ # : A file is deleted in the watched directory.
134
+ #
135
+ # `:delete_self`
136
+ # : The watched file or directory itself is deleted.
137
+ #
138
+ # `:move_self`
139
+ # : The watched file or directory itself is moved.
140
+ #
141
+ # ### Helper Flags
142
+ #
143
+ # These flags are just combinations of the flags above.
144
+ #
145
+ # `:close`
146
+ # : Either `:close_write` or `:close_nowrite` is activated.
147
+ #
148
+ # `:move`
149
+ # : Either `:moved_from` or `:moved_to` is activated.
150
+ #
151
+ # `:all_events`
152
+ # : Any event above is activated.
153
+ #
154
+ # ### Options Flags
155
+ #
156
+ # These flags don't actually specify events.
157
+ # Instead, they specify options for the watcher.
158
+ #
159
+ # `:onlydir`
160
+ # : Only watch the path if it's a directory.
161
+ #
162
+ # `:dont_follow`
163
+ # : Don't follow symlinks.
164
+ #
165
+ # `:mask_add`
166
+ # : Add these flags to the pre-existing flags for this path.
167
+ #
168
+ # `:oneshot`
169
+ # : Only send the event once, then shut down the watcher.
170
+ #
171
+ # `:recursive`
172
+ # : Recursively watch any subdirectories that are created.
173
+ # Note that this is a feature of rb-inotify,
174
+ # rather than of inotify itself, which can only watch one level of a directory.
175
+ # This means that the {Event#name} field
176
+ # will contain only the basename of the modified file.
177
+ # When using `:recursive`, {Event#absolute_name} should always be used.
178
+ #
179
+ # @param path [String] The path to the file or directory
180
+ # @param flags [Array<Symbol>] Which events to watch for
181
+ # @yield [event] A block that will be called
182
+ # whenever one of the specified events occur
183
+ # @yieldparam event [Event] The Event object containing information
184
+ # about the event that occured
185
+ # @return [Watcher] A Watcher set up to watch this path for these events
186
+ # @raise [SystemCallError] if the file or directory can't be watched,
187
+ # e.g. if the file isn't found, read access is denied,
188
+ # or the flags don't contain any events
189
+ def watch(path, *flags, &callback)
190
+ return Watcher.new(self, path, *flags, &callback) unless flags.include?(:recursive)
191
+
192
+ Dir.glob(File.join(path, '*'), File::FNM_DOTMATCH).each do |d|
193
+ binary_d = d.respond_to?(:force_encoding) ? d.dup.force_encoding('BINARY') : d
194
+ next if binary_d =~ /\/\.\.?$/ # Current or parent directory
195
+ watch(d, *flags, &callback) if !RECURSIVE_BLACKLIST.include?(d) && File.directory?(d)
196
+ end
197
+
198
+ rec_flags = [:create, :moved_to]
199
+ return watch(path, *((flags - [:recursive]) | rec_flags)) do |event|
200
+ callback.call(event) if flags.include?(:all_events) || !(flags & event.flags).empty?
201
+ next if (rec_flags & event.flags).empty? || !event.flags.include?(:isdir)
202
+ begin
203
+ watch(event.absolute_name, *flags, &callback)
204
+ rescue Errno::ENOENT
205
+ # If the file has been deleted since the glob was run, we don't want to error out.
206
+ end
207
+ end
208
+ end
209
+
210
+ # Starts the notifier watching for filesystem events.
211
+ # Blocks until \{#stop} is called.
212
+ #
213
+ # @see #process
214
+ def run
215
+ @stop = false
216
+ process until @stop
217
+ end
218
+
219
+ # Stop watching for filesystem events.
220
+ # That is, if we're in a \{#run} loop,
221
+ # exit out as soon as we finish handling the events.
222
+ def stop
223
+ @stop = true
224
+ end
225
+
226
+ # Blocks until there are one or more filesystem events
227
+ # that this notifier has watchers registered for.
228
+ # Once there are events, the appropriate callbacks are called
229
+ # and this function returns.
230
+ #
231
+ # @see #run
232
+ def process
233
+ read_events.each {|event| event.callback!}
234
+ end
235
+
236
+ # Close the notifier.
237
+ #
238
+ # @raise [SystemCallError] if closing the underlying file descriptor fails.
239
+ def close
240
+ return if Native.close(@fd) == 0
241
+
242
+ raise SystemCallError.new("Failed to properly close inotify socket" +
243
+ case FFI.errno
244
+ when Errno::EBADF::Errno; ": invalid or closed file descriptior"
245
+ when Errno::EIO::Errno; ": an I/O error occured"
246
+ end,
247
+ FFI.errno)
248
+ end
249
+
250
+ private
251
+
252
+ # Blocks until there are one or more filesystem events
253
+ # that this notifier has watchers registered for.
254
+ # Once there are events, returns their {Event} objects.
255
+ def read_events
256
+ size = 64 * Native::Event.size
257
+ tries = 1
258
+
259
+ begin
260
+ data = readpartial(size)
261
+ rescue SystemCallError => er
262
+ # EINVAL means that there's more data to be read
263
+ # than will fit in the buffer size
264
+ raise er unless er.errno == Errno::EINVAL::Errno || tries == 5
265
+ size *= 2
266
+ tries += 1
267
+ retry
268
+ end
269
+
270
+ events = []
271
+ cookies = {}
272
+ while ev = Event.consume(data, self)
273
+ events << ev
274
+ next if ev.cookie == 0
275
+ cookies[ev.cookie] ||= []
276
+ cookies[ev.cookie] << ev
277
+ end
278
+ cookies.each {|c, evs| evs.each {|ev| ev.related.replace(evs - [ev]).freeze}}
279
+ events
280
+ end
281
+
282
+ # Same as IO#readpartial, or as close as we need.
283
+ def readpartial(size)
284
+ # Use Ruby's readpartial if possible, to avoid blocking other threads.
285
+ return to_io.readpartial(size) if self.class.supports_ruby_io?
286
+
287
+ tries = 0
288
+ begin
289
+ tries += 1
290
+ buffer = FFI::MemoryPointer.new(:char, size)
291
+ size_read = Native.read(fd, buffer, size)
292
+ return buffer.read_string(size_read) if size_read >= 0
293
+ end while FFI.errno == Errno::EINTR::Errno && tries <= 5
294
+
295
+ raise SystemCallError.new("Error reading inotify events" +
296
+ case FFI.errno
297
+ when Errno::EAGAIN::Errno; ": no data available for non-blocking I/O"
298
+ when Errno::EBADF::Errno; ": invalid or closed file descriptor"
299
+ when Errno::EFAULT::Errno; ": invalid buffer"
300
+ when Errno::EINVAL::Errno; ": invalid file descriptor"
301
+ when Errno::EIO::Errno; ": I/O error"
302
+ when Errno::EISDIR::Errno; ": file descriptor is a directory"
303
+ else; ""
304
+ end,
305
+ FFI.errno)
306
+ end
307
+ end
308
+ end