moduleINotify# Notifier wraps a single instance of inotify.# It's possible to have more than one instance,# but usually unnecessary.## @example# # Create the notifier# notifier = INotify::Notifier.new## # Run this callback whenever the file path/to/foo.txt is read# notifier.watch("path/to/foo.txt", :access) do# puts "Foo.txt was accessed!"# end## # Watch for any file in the directory being deleted# # or moved out of the directory.# notifier.watch("path/to/directory", :delete, :moved_from) do |event|# # The #name field of the event object contains the name of the affected file# puts "#{event.name} is no longer in the directory!"# end## # Nothing happens until you run the notifier!# notifier.runclassNotifier# A hash from {Watcher} ids to the instances themselves.## @private# @return [{Fixnum => Watcher}]attr_reader:watchers# The underlying file descriptor for this notifier.# This is a valid OS file descriptor, and can be used as such# (except under JRuby -- see \{#to\_io}).## @return [Fixnum]attr_reader:fd# Creates a new {Notifier}.## @return [Notifier]# @raise [SystemCallError] if inotify failed to initialize for some reasondefinitialize@fd=Native.inotify_init@watchers={}returnunless@fd<0raiseSystemCallError.new("Failed to initialize inotify"+caseFFI.errnowhenErrno::EMFILE::Errno;": the user limit on the total number of inotify instances has been reached."whenErrno::ENFILE::Errno;": the system limit on the total number of file descriptors has been reached."whenErrno::ENOMEM::Errno;": insufficient kernel memory is available."else;""end,FFI.errno)end# Returns a Ruby IO object wrapping the underlying file descriptor.# Since this file descriptor is fully functional (except under JRuby),# this IO object can be used in any way a Ruby-created IO object can.# This includes passing it to functions like `#select`.## Note that this always returns the same IO object.# Creating lots of IO objects for the same file descriptor# can cause some odd problems.## **This is not supported under JRuby**.# JRuby currently doesn't use native file descriptors for the IO object,# so we can't use this file descriptor as a stand-in.## @return [IO] An IO object wrapping the file descriptor# @raise [NotImplementedError] if this is being called in JRubydefto_ioraiseNotImplementedError.new("INotify::Notifier#to_io is not supported under JRuby")ifRUBY_PLATFORM=~/java/@io||=IO.new(@fd)end# Watches a file or directory for changes,# calling the callback when there are.# This is only activated once \{#process} or \{#run} is called.## **Note that by default, this does not recursively watch subdirectories# of the watched directory**.# To do so, use the `:recursive` flag.## ## Flags## `:access`# : A file is accessed (that is, read).## `:attrib`# : A file's metadata is changed (e.g. permissions, timestamps, etc).## `:close_write`# : A file that was opened for writing is closed.## `:close_nowrite`# : A file that was not opened for writing is closed.## `:modify`# : A file is modified.## `:open`# : A file is opened.## ### Directory-Specific Flags## These flags only apply when a directory is being watched.## `:moved_from`# : A file is moved out of the watched directory.## `:moved_to`# : A file is moved into the watched directory.## `:create`# : A file is created in the watched directory.## `:delete`# : A file is deleted in the watched directory.## `:delete_self`# : The watched file or directory itself is deleted.## `:move_self`# : The watched file or directory itself is moved.## ### Helper Flags## These flags are just combinations of the flags above.## `:close`# : Either `:close_write` or `:close_nowrite` is activated.## `:move`# : Either `:moved_from` or `:moved_to` is activated.## `:all_events`# : Any event above is activated.## ### Options Flags## These flags don't actually specify events.# Instead, they specify options for the watcher.## `:onlydir`# : Only watch the path if it's a directory.## `:dont_follow`# : Don't follow symlinks.## `:mask_add`# : Add these flags to the pre-existing flags for this path.## `:oneshot`# : Only send the event once, then shut down the watcher.## `:recursive`# : Recursively watch any subdirectories that are created.# Note that this is a feature of rb-inotify,# rather than of inotify itself, which can only watch one level of a directory.# This means that the {Event#name} field# will contain only the basename of the modified file.# When using `:recursive`, {Event#absolute_name} should always be used.## @param path [String] The path to the file or directory# @param flags [Array<Symbol>] Which events to watch for# @yield [event] A block that will be called# whenever one of the specified events occur# @yieldparam event [Event] The Event object containing information# about the event that occured# @return [Watcher] A Watcher set up to watch this path for these events# @raise [SystemCallError] if the file or directory can't be watched,# e.g. if the file isn't found, read access is denied,# or the flags don't contain any eventsdefwatch(path,*flags,&callback)returnWatcher.new(self,path,*flags,&callback)unlessflags.include?(:recursive)Dir.entries(path).eachdo|d|nextifd=='.'||d=='..'d=File.join(path,d)watch(d,*flags,&callback)ifFile.directory?(d)endrec_flags=[:create,:moved_to]returnwatch(path,*((flags-[:recursive])|rec_flags))do|event|callback.call(event)ifflags.include?(:all_events)||!(flags&event.flags).empty?nextif(rec_flags&event.flags).empty?||!event.flags.include?(:isdir)watch(event.absolute_name,*flags,&callback)endend# Starts the notifier watching for filesystem events.# Blocks until \{#stop} is called.## @see #processdefrun@stop=falseprocessuntil@stopend# Stop watching for filesystem events.# That is, if we're in a \{#run} loop,# exit out as soon as we finish handling the events.defstop@stop=trueend# Blocks until there are one or more filesystem events# that this notifier has watchers registered for.# Once there are events, the appropriate callbacks are called# and this function returns.## @see #rundefprocessread_events.each{|event|event.callback!}end# Blocks until there are one or more filesystem events# that this notifier has watchers registered for.# Once there are events, returns their {Event} objects.## @privatedefread_eventssize=64*Native::Event.sizetries=1begindata=readpartial(size)rescueSystemCallError=>er# EINVAL means that there's more data to be read# than will fit in the buffer sizeraiseerunlesser.errno==EINVAL||tries==5size*=2tries+=1retryendevents=[]cookies={}whileev=Event.consume(data,self)events<<evnextifev.cookie==0cookies[ev.cookie]||=[]cookies[ev.cookie]<<evendcookies.each{|c,evs|evs.each{|ev|ev.related.replace(evs-[ev]).freeze}}eventsenddefclosereturnifNative.close(@fd)==0raiseSystemCallError.new("Failed to properly close inotify socket"+caseFFI.errnowhenErrno::EBADF::Errno;": invalid or closed file descriptior"whenErrno::EIO::Errno;": an I/O error occured"end,FFI.errno)endprivate# Same as IO#readpartial, or as close as we need.defreadpartial(size)buffer=FFI::MemoryPointer.new(:char,size)size_read=Native.read(fd,buffer,size)returnbuffer.read_string(size_read)ifsize_read>=0raiseSystemCallError.new("Error reading inotify events"+caseFFI.errnowhenErrno::EAGAIN::Errno;": no data available for non-blocking I/O"whenErrno::EBADF::Errno;": invalid or closed file descriptor"whenErrno::EFAULT::Errno;": invalid buffer"whenErrno::EINVAL::Errno;": invalid file descriptor"whenErrno::EIO::Errno;": I/O error"whenErrno::EISDIR::Errno;": file descriptor is a directory"else;""end,FFI.errno)endendend