module INotify # An event caused by a change on the filesystem. # Each {Watcher} can fire many events, # which are passed to that watcher's callback. class Event # A list of other events that are related to this one. # Currently, this is only used for files that are moved within the same directory: # the `:moved_from` and the `:moved_to` events will be related. # # @return [Array] attr_reader :related # The name of the file that the event occurred on. # This is only set for events that occur on files in directories; # otherwise, it's `""`. # Similarly, if the event is being fired for the directory itself # the name will be `""` # # This pathname is relative to the enclosing directory. # For the absolute pathname, use \{#absolute\_name}. # Note that when the `:recursive` flag is passed to {Notifier#watch}, # events in nested subdirectories will still have a `#name` field # relative to their immediately enclosing directory. # For example, an event on the file `"foo/bar/baz"` # will have name `"baz"`. # # @return [String] attr_reader :name # The {Notifier} that fired this event. # # @return [Notifier] attr_reader :notifier # An integer specifying that this event is related to some other event, # which will have the same cookie. # # Currently, this is only used for files that are moved within the same directory. # Both the `:moved_from` and the `:moved_to` events will have the same cookie. # # @private # @return [Fixnum] attr_reader :cookie # The {Watcher#id id} of the {Watcher} that fired this event. # # @private # @return [Fixnum] attr_reader :watcher_id # Returns the {Watcher} that fired this event. # # @return [Watcher] def watcher @watcher ||= @notifier.watchers[@watcher_id] end # The absolute path of the file that the event occurred on. # # This is actually only as absolute as the path passed to the {Watcher} # that created this event. # However, it is relative to the working directory, # assuming that hasn't changed since the watcher started. # # @return [String] def absolute_name return watcher.path if name.empty? return File.join(watcher.path, name) end # Returns the flags that describe this event. # This is generally similar to the input to {Notifier#watch}, # except that it won't contain options flags nor `:all_events`, # and it may contain one or more of the following flags: # # `:unmount` # : The filesystem containing the watched file or directory was unmounted. # # `:ignored` # : The \{#watcher watcher} was closed, or the watched file or directory was deleted. # # `:isdir` # : The subject of this event is a directory. # # @return [Array] def flags @flags ||= Native::Flags.from_mask(@native[:mask]) end # Constructs an {Event} object from a string of binary data, # and destructively modifies the string to get rid of the initial segment # used to construct the Event. # # @private # @param data [String] The string to be modified # @param notifier [Notifier] The {Notifier} that fired the event # @return [Event, nil] The event, or `nil` if the string is empty def self.consume(data, notifier) return nil if data.empty? ev = new(data, notifier) data.replace data[ev.size..-1] ev end # Creates an event from a string of binary data. # Differs from {Event.consume} in that it doesn't modify the string. # # @private # @param data [String] The data string # @param notifier [Notifier] The {Notifier} that fired the event def initialize(data, notifier) ptr = FFI::MemoryPointer.from_string(data) @native = Native::Event.new(ptr) @related = [] @cookie = @native[:cookie] @name = fix_encoding(data[@native.size, @native[:len]].gsub(/\0+$/, '')) @notifier = notifier @watcher_id = @native[:wd] raise QueueOverflowError.new("inotify event queue has overflowed.") if @native[:mask] & Native::Flags::IN_Q_OVERFLOW != 0 end # Calls the callback of the watcher that fired this event, # passing in the event itself. # # @private def callback! watcher && watcher.callback!(self) end # Returns the size of this event object in bytes, # including the \{#name} string. # # @return [Fixnum] def size @native.size + @native[:len] end private def fix_encoding(name) name.force_encoding('filesystem') if name.respond_to?(:force_encoding) name end end end