filewatch 0.2.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/globtail +55 -0
- data/lib/filewatch/buftok.rb +2 -2
- data/lib/filewatch/tail.rb +171 -48
- data/lib/filewatch/watch.rb +145 -26
- data/test/globtail/Makefile +7 -0
- data/test/globtail/framework.sh +58 -0
- data/test/globtail/test1.data +5 -0
- data/test/globtail/test1.sh +17 -0
- data/test/globtail/test10.data +4 -0
- data/test/globtail/test10.sh +20 -0
- data/test/globtail/test2.data +2 -0
- data/test/globtail/test2.sh +17 -0
- data/test/globtail/test3.data +3 -0
- data/test/globtail/test3.sh +18 -0
- data/test/globtail/test4.data +4 -0
- data/test/globtail/test4.sh +16 -0
- data/test/globtail/test5.data +6 -0
- data/test/globtail/test5.sh +25 -0
- data/test/globtail/test6.data +6 -0
- data/test/globtail/test6.sh +29 -0
- data/test/globtail/test7.data +5 -0
- data/test/globtail/test7.sh +24 -0
- data/test/globtail/test8.data +5 -0
- data/test/globtail/test8.sh +23 -0
- data/test/globtail/test9.data +3 -0
- data/test/globtail/test9.sh +22 -0
- metadata +38 -38
- data/bin/gtail +0 -50
- data/lib/filewatch/exception.rb +0 -12
- data/lib/filewatch/inotify/emhandler.rb +0 -16
- data/lib/filewatch/inotify/event.rb +0 -101
- data/lib/filewatch/inotify/fd.rb +0 -319
- data/lib/filewatch/namespace.rb +0 -3
- data/lib/filewatch/rubyfixes.rb +0 -8
- data/lib/filewatch/stringpipeio.rb +0 -33
- data/lib/filewatch/tailglob.rb +0 -244
- data/lib/filewatch/watchglob.rb +0 -83
- data/test/log4j/LogTest.java +0 -21
- data/test/log4j/log4j.properties +0 -20
- data/test/logrotate/logrotate.conf +0 -5
    
        data/bin/globtail
    ADDED
    
    | @@ -0,0 +1,55 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "filewatch/tail"
         | 
| 4 | 
            +
            require "optparse"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            progname = File.basename($0)
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            config = {
         | 
| 9 | 
            +
              :verbose => false,
         | 
| 10 | 
            +
            }
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            opts = OptionParser.new do |opts|
         | 
| 13 | 
            +
              opts.banner = "#{progname} [-v] [-s path] [-i interval] [-x glob] path/glob ..."
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              opts.on("-v", "--verbose", "Enable verbose/debug output") do
         | 
| 16 | 
            +
                config[:verbose] = true
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              opts.on("-x", "--exclude PATH", String, "path to exclude from watching") do |path|
         | 
| 20 | 
            +
                config[:exclude] ||= []
         | 
| 21 | 
            +
                config[:exclude] << path
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              opts.on("-s", "--sincedb PATH", String, "Sincedb path") do |path|
         | 
| 25 | 
            +
                config[:sincedb_path] = path
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              opts.on("-i", "--interval SECONDS", Integer,
         | 
| 29 | 
            +
                      "Sincedb write interval") do |path|
         | 
| 30 | 
            +
                config[:sincedb_write_interval] = path
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
            end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            begin
         | 
| 35 | 
            +
              opts.order!
         | 
| 36 | 
            +
            rescue OptionParser::InvalidOption
         | 
| 37 | 
            +
              $stderr.puts "#{progname}: #{$!}"
         | 
| 38 | 
            +
              $stderr.puts opts.usage
         | 
| 39 | 
            +
            end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            logger = Logger.new(STDERR)
         | 
| 42 | 
            +
            logger.progname = progname
         | 
| 43 | 
            +
            logger.level = config[:verbose] ? Logger::DEBUG : Logger::INFO
         | 
| 44 | 
            +
            config[:logger] = logger
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            tail = FileWatch::Tail.new(config)
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            ARGV.each { |path| tail.tail(path) }
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            Signal.trap("EXIT") { tail.sincedb_write("globtail exiting") }
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            $stdout.sync = true
         | 
| 53 | 
            +
            tail.subscribe do |path, line|
         | 
| 54 | 
            +
              puts "#{path}: #{line}"
         | 
| 55 | 
            +
            end
         | 
    
        data/lib/filewatch/buftok.rb
    CHANGED
    
    | @@ -29,7 +29,7 @@ | |
| 29 29 | 
             
            #    end
         | 
| 30 30 | 
             
            #  end
         | 
| 31 31 |  | 
| 32 | 
            -
            class BufferedTokenizer
         | 
| 32 | 
            +
            module FileWatch; class BufferedTokenizer
         | 
| 33 33 | 
             
              # New BufferedTokenizers will operate on lines delimited by "\n" by default
         | 
| 34 34 | 
             
              # or allow you to specify any delimiter token you so choose, which will then
         | 
| 35 35 | 
             
              # be used by String#split to tokenize the input data
         | 
| @@ -136,4 +136,4 @@ cter token support. | |
| 136 136 | 
             
              def empty?
         | 
| 137 137 | 
             
                @input.empty?
         | 
| 138 138 | 
             
              end
         | 
| 139 | 
            -
            end
         | 
| 139 | 
            +
            end; end
         | 
    
        data/lib/filewatch/tail.rb
    CHANGED
    
    | @@ -1,58 +1,181 @@ | |
| 1 | 
            +
            require "filewatch/buftok"
         | 
| 1 2 | 
             
            require "filewatch/watch"
         | 
| 2 | 
            -
            require " | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
               | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
                  @ | 
| 20 | 
            -
                  
         | 
| 21 | 
            -
                   | 
| 22 | 
            -
                   | 
| 23 | 
            -
                  @ | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
                   | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 3 | 
            +
            require "logger"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module FileWatch
         | 
| 6 | 
            +
              class Tail
         | 
| 7 | 
            +
                attr_accessor :logger
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                public
         | 
| 10 | 
            +
                def initialize(opts={})
         | 
| 11 | 
            +
                  if opts[:logger]
         | 
| 12 | 
            +
                    @logger = opts[:logger]
         | 
| 13 | 
            +
                  else
         | 
| 14 | 
            +
                    @logger = Logger.new(STDERR)
         | 
| 15 | 
            +
                    @logger.level = Logger::INFO
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                  @files = {}
         | 
| 18 | 
            +
                  @buffers = {}
         | 
| 19 | 
            +
                  @watch = FileWatch::Watch.new
         | 
| 20 | 
            +
                  @watch.logger = @logger
         | 
| 21 | 
            +
                  @sincedb = {}
         | 
| 22 | 
            +
                  @sincedb_last_write = 0
         | 
| 23 | 
            +
                  @statcache = {}
         | 
| 24 | 
            +
                  @opts = {
         | 
| 25 | 
            +
                    :sincedb_write_interval => 10,
         | 
| 26 | 
            +
                    :sincedb_path => "#{ENV["HOME"]}/.sincedb",
         | 
| 27 | 
            +
                    :exclude => [],
         | 
| 28 | 
            +
                  }.merge(opts)
         | 
| 29 | 
            +
                  @watch.exclude(@opts[:exclude])
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  _sincedb_open
         | 
| 32 | 
            +
                end # def initialize
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                public
         | 
| 35 | 
            +
                def logger=(logger)
         | 
| 36 | 
            +
                  @logger = logger
         | 
| 37 | 
            +
                  @watch.logger = logger
         | 
| 38 | 
            +
                end # def logger=
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                public
         | 
| 41 | 
            +
                def tail(path)
         | 
| 42 | 
            +
                  @watch.watch(path)
         | 
| 43 | 
            +
                end # def tail
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                public
         | 
| 46 | 
            +
                def subscribe(&block)
         | 
| 47 | 
            +
                  # subscribe(stat_interval = 1, discover_interval = 5, &block)
         | 
| 48 | 
            +
                  @watch.subscribe do |event, path|
         | 
| 49 | 
            +
                    case event
         | 
| 50 | 
            +
                    when :create, :create_initial
         | 
| 51 | 
            +
                      if @files.member?(path)
         | 
| 52 | 
            +
                        @logger.debug("#{event} for #{path}: already exists in @files")
         | 
| 53 | 
            +
                        next
         | 
| 39 54 | 
             
                      end
         | 
| 55 | 
            +
                      _open_file(path, event)
         | 
| 56 | 
            +
                      _read_file(path, &block)
         | 
| 57 | 
            +
                    when :modify
         | 
| 58 | 
            +
                      if !@files.member?(path)
         | 
| 59 | 
            +
                        @logger.debug(":modify for #{path}, does not exist in @files")
         | 
| 60 | 
            +
                        _open_file(path)
         | 
| 61 | 
            +
                      end
         | 
| 62 | 
            +
                      _read_file(path, &block)
         | 
| 63 | 
            +
                    when :delete
         | 
| 64 | 
            +
                      @logger.debug(":delete for #{path}, deleted from @files")
         | 
| 65 | 
            +
                      _read_file(path, &block)
         | 
| 66 | 
            +
                      @files[path].close
         | 
| 67 | 
            +
                      @files.delete(path)
         | 
| 68 | 
            +
                      @statcache.delete(path)
         | 
| 69 | 
            +
                    else
         | 
| 70 | 
            +
                      @logger.warn("unknown event type #{event} for #{path}")
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
                  end # @watch.subscribe
         | 
| 73 | 
            +
                end # def each
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                private
         | 
| 76 | 
            +
                def _open_file(path, event)
         | 
| 77 | 
            +
                  @logger.debug("_open_file: #{path}: opening")
         | 
| 78 | 
            +
                  # TODO(petef): handle File.open failing
         | 
| 79 | 
            +
                  begin
         | 
| 80 | 
            +
                    @files[path] = File.open(path)
         | 
| 81 | 
            +
                  rescue Errno::ENOENT
         | 
| 82 | 
            +
                    @logger.warn("#{path}: open: #{$!}")
         | 
| 83 | 
            +
                    @files.delete(path)
         | 
| 84 | 
            +
                    return
         | 
| 85 | 
            +
                  end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                  stat = File::Stat.new(path)
         | 
| 88 | 
            +
                  inode = [stat.ino, stat.dev_major, stat.dev_minor]
         | 
| 89 | 
            +
                  @statcache[path] = inode
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  if @sincedb.member?(inode)
         | 
| 92 | 
            +
                    last_size = @sincedb[inode]
         | 
| 93 | 
            +
                    @logger.debug("#{path}: sincedb last value #{@sincedb[inode]}, cur size #{stat.size}")
         | 
| 94 | 
            +
                    if last_size <= stat.size
         | 
| 95 | 
            +
                      @logger.debug("#{path}: sincedb: seeking to #{last_size}")
         | 
| 96 | 
            +
                      @files[path].sysseek(last_size, IO::SEEK_SET)
         | 
| 97 | 
            +
                    else
         | 
| 98 | 
            +
                      @logger.debug("#{path}: last value size is greater than current value, starting over")
         | 
| 99 | 
            +
                      @sincedb[inode] = 0
         | 
| 40 100 | 
             
                    end
         | 
| 101 | 
            +
                  elsif event == :create_initial && @files[path]
         | 
| 102 | 
            +
                    @logger.debug("#{path}: initial create, no sincedb, seeking to end #{stat.size}")
         | 
| 103 | 
            +
                    @files[path].sysseek(stat.size, IO::SEEK_SET)
         | 
| 104 | 
            +
                    @sincedb[inode] = stat.size
         | 
| 41 105 | 
             
                  else
         | 
| 42 | 
            -
                     | 
| 106 | 
            +
                    @logger.debug("#{path}: staying at position 0, no sincedb")
         | 
| 43 107 | 
             
                  end
         | 
| 108 | 
            +
                end # def _open_file
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                private
         | 
| 111 | 
            +
                def _read_file(path, &block)
         | 
| 112 | 
            +
                  @buffers[path] ||= FileWatch::BufferedTokenizer.new
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                  changed = false
         | 
| 115 | 
            +
                  loop do
         | 
| 116 | 
            +
                    begin
         | 
| 117 | 
            +
                      data = @files[path].read_nonblock(4096)
         | 
| 118 | 
            +
                      changed = true
         | 
| 119 | 
            +
                      @buffers[path].extract(data).each do |line|
         | 
| 120 | 
            +
                        yield(path, line)
         | 
| 121 | 
            +
                      end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                      @sincedb[@statcache[path]] = @files[path].pos
         | 
| 124 | 
            +
                    rescue Errno::EWOULDBLOCK, Errno::EINTR, EOFError
         | 
| 125 | 
            +
                      break
         | 
| 126 | 
            +
                    end
         | 
| 127 | 
            +
                  end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                  if changed
         | 
| 130 | 
            +
                    now = Time.now.to_i
         | 
| 131 | 
            +
                    delta = now - @sincedb_last_write
         | 
| 132 | 
            +
                    if delta >= @opts[:sincedb_write_interval]
         | 
| 133 | 
            +
                      @logger.debug("writing sincedb (delta since last write = #{delta})")
         | 
| 134 | 
            +
                      _sincedb_write
         | 
| 135 | 
            +
                      @sincedb_last_write = now
         | 
| 136 | 
            +
                    end
         | 
| 137 | 
            +
                  end
         | 
| 138 | 
            +
                end # def _read_file
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                public
         | 
| 141 | 
            +
                def sincedb_write(reason=nil)
         | 
| 142 | 
            +
                  @logger.debug("caller requested sincedb write (#{reason})")
         | 
| 143 | 
            +
                  _sincedb_write
         | 
| 44 144 | 
             
                end
         | 
| 45 | 
            -
              end # def subscribe
         | 
| 46 145 |  | 
| 47 | 
            -
             | 
| 48 | 
            -
                 | 
| 146 | 
            +
                private
         | 
| 147 | 
            +
                def _sincedb_open
         | 
| 148 | 
            +
                  path = @opts[:sincedb_path]
         | 
| 49 149 | 
             
                  begin
         | 
| 50 | 
            -
                     | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
                     | 
| 150 | 
            +
                    db = File.open(path)
         | 
| 151 | 
            +
                  rescue
         | 
| 152 | 
            +
                    @logger.debug("_sincedb_open: #{path}: #{$!}")
         | 
| 153 | 
            +
                    return
         | 
| 54 154 | 
             
                  end
         | 
| 55 | 
            -
                end
         | 
| 56 | 
            -
              end
         | 
| 57 155 |  | 
| 58 | 
            -
             | 
| 156 | 
            +
                  @logger.debug("_sincedb_open: reading from #{path}")
         | 
| 157 | 
            +
                  db.each do |line|
         | 
| 158 | 
            +
                    ino, dev_major, dev_minor, pos = line.split(" ", 4)
         | 
| 159 | 
            +
                    inode = [ino.to_i, dev_major.to_i, dev_minor.to_i]
         | 
| 160 | 
            +
                    @logger.debug("_sincedb_open: setting #{inode.inspect} to #{pos.to_i}")
         | 
| 161 | 
            +
                    @sincedb[inode] = pos.to_i
         | 
| 162 | 
            +
                  end
         | 
| 163 | 
            +
                end # def _sincedb_open
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                private
         | 
| 166 | 
            +
                def _sincedb_write
         | 
| 167 | 
            +
                  path = @opts[:sincedb_path]
         | 
| 168 | 
            +
                  begin
         | 
| 169 | 
            +
                    db = File.open(path, "w")
         | 
| 170 | 
            +
                  rescue
         | 
| 171 | 
            +
                    @logger.debug("_sincedb_write: #{path}: #{$!}")
         | 
| 172 | 
            +
                    return
         | 
| 173 | 
            +
                  end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                  @sincedb.each do |inode, pos|
         | 
| 176 | 
            +
                    db.puts([inode, pos].flatten.join(" "))
         | 
| 177 | 
            +
                  end
         | 
| 178 | 
            +
                  db.close
         | 
| 179 | 
            +
                end # def _sincedb_write
         | 
| 180 | 
            +
              end # class Watch
         | 
| 181 | 
            +
            end # module FileWatch
         | 
    
        data/lib/filewatch/watch.rb
    CHANGED
    
    | @@ -1,26 +1,145 @@ | |
| 1 | 
            -
            require " | 
| 2 | 
            -
             | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
                 | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 1 | 
            +
            require "logger"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module FileWatch
         | 
| 4 | 
            +
              class Watch
         | 
| 5 | 
            +
                attr_accessor :logger
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                public
         | 
| 8 | 
            +
                def initialize(opts={})
         | 
| 9 | 
            +
                  if opts[:logger]
         | 
| 10 | 
            +
                    @logger = opts[:logger]
         | 
| 11 | 
            +
                  else
         | 
| 12 | 
            +
                    @logger = Logger.new(STDERR)
         | 
| 13 | 
            +
                    @logger.level = Logger::INFO
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                  @watching = []
         | 
| 16 | 
            +
                  @exclude = []
         | 
| 17 | 
            +
                  @files = Hash.new { |h, k| h[k] = Hash.new }
         | 
| 18 | 
            +
                end # def initialize
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                public
         | 
| 21 | 
            +
                def logger=(logger)
         | 
| 22 | 
            +
                  @logger = logger
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                public
         | 
| 26 | 
            +
                def exclude(path)
         | 
| 27 | 
            +
                  path.to_a.each { |p| @exclude << p }
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                public
         | 
| 31 | 
            +
                def watch(path)
         | 
| 32 | 
            +
                  if ! @watching.member?(path)
         | 
| 33 | 
            +
                    @watching << path
         | 
| 34 | 
            +
                    _discover_file(path, true)
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  return true
         | 
| 38 | 
            +
                end # def tail
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                # Calls &block with params [event_type, path]
         | 
| 41 | 
            +
                # event_type can be one of:
         | 
| 42 | 
            +
                #   :create_initial - initially present file (so start at end for tail)
         | 
| 43 | 
            +
                #   :create - file is created (new file after initial globs, start at 0)
         | 
| 44 | 
            +
                #   :modify - file is modified (size increases)
         | 
| 45 | 
            +
                #   :delete - file is deleted
         | 
| 46 | 
            +
                public
         | 
| 47 | 
            +
                def each(&block)
         | 
| 48 | 
            +
                  # Send any creates.
         | 
| 49 | 
            +
                  @files.keys.each do |path|
         | 
| 50 | 
            +
                    if ! @files[path][:create_sent]
         | 
| 51 | 
            +
                      if @files[path][:initial]
         | 
| 52 | 
            +
                        yield(:create_initial, path)
         | 
| 53 | 
            +
                      else
         | 
| 54 | 
            +
                        yield(:create, path)
         | 
| 55 | 
            +
                      end
         | 
| 56 | 
            +
                      @files[path][:create_sent] = true
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  @files.keys.each do |path|
         | 
| 61 | 
            +
                    begin
         | 
| 62 | 
            +
                      stat = File::Stat.new(path)
         | 
| 63 | 
            +
                    rescue Errno::ENOENT
         | 
| 64 | 
            +
                      # file has gone away or we can't read it anymore.
         | 
| 65 | 
            +
                      @files.delete(path)
         | 
| 66 | 
            +
                      @logger.debug("#{path}: stat failed (#{$!}), deleting from @files")
         | 
| 67 | 
            +
                      yield(:delete, path)
         | 
| 68 | 
            +
                      next
         | 
| 69 | 
            +
                    end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                    inode = [stat.ino, stat.dev_major, stat.dev_minor]
         | 
| 72 | 
            +
                    if inode != @files[path][:inode]
         | 
| 73 | 
            +
                      @logger.debug("#{path}: old inode was #{@files[path][:inode].inspect}, new is #{inode.inspect}")
         | 
| 74 | 
            +
                      yield(:delete, path)
         | 
| 75 | 
            +
                      yield(:create, path)
         | 
| 76 | 
            +
                    elsif stat.size < @files[path][:size]
         | 
| 77 | 
            +
                      @logger.debug("#{path}: file rolled, new size is #{stat.size}, old size #{@files[path][:size]}")
         | 
| 78 | 
            +
                      yield(:delete, path)
         | 
| 79 | 
            +
                      yield(:create, path)
         | 
| 80 | 
            +
                    elsif stat.size > @files[path][:size]
         | 
| 81 | 
            +
                      @logger.debug("#{path}: file grew, old size #{@files[path][:size]}, new size #{stat.size}")
         | 
| 82 | 
            +
                      yield(:modify, path)
         | 
| 83 | 
            +
                    end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                    @files[path][:size] = stat.size
         | 
| 86 | 
            +
                    @files[path][:inode] = inode
         | 
| 87 | 
            +
                  end # @files.keys.each
         | 
| 88 | 
            +
                end # def each
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                public
         | 
| 91 | 
            +
                def discover
         | 
| 92 | 
            +
                  @watching.each do |path|
         | 
| 93 | 
            +
                    _discover_file(path)
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                public
         | 
| 98 | 
            +
                def subscribe(stat_interval = 1, discover_interval = 5, &block)
         | 
| 99 | 
            +
                  glob = 0
         | 
| 100 | 
            +
                  loop do
         | 
| 101 | 
            +
                    each(&block)
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                    glob += 1
         | 
| 104 | 
            +
                    if glob == discover_interval
         | 
| 105 | 
            +
                      discover
         | 
| 106 | 
            +
                      glob = 0
         | 
| 107 | 
            +
                    end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                    sleep(stat_interval)
         | 
| 110 | 
            +
                  end
         | 
| 111 | 
            +
                end # def subscribe
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                private
         | 
| 114 | 
            +
                def _discover_file(path, initial=false)
         | 
| 115 | 
            +
                  Dir.glob(path).each do |file|
         | 
| 116 | 
            +
                    next if @files.member?(file)
         | 
| 117 | 
            +
                    next unless File.file?(file)
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                    @logger.debug("_discover_file: #{path}: new: #{file} (exclude is #{@exclude.inspect})")
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                    skip = false
         | 
| 122 | 
            +
                    @exclude.each do |pattern|
         | 
| 123 | 
            +
                      if File.fnmatch?(pattern, File.basename(file))
         | 
| 124 | 
            +
                        @logger.debug("_discover_file: #{file}: skipping because it " +
         | 
| 125 | 
            +
                                      "matches exclude #{pattern}")
         | 
| 126 | 
            +
                        skip = true
         | 
| 127 | 
            +
                        break
         | 
| 128 | 
            +
                      end
         | 
| 129 | 
            +
                    end
         | 
| 130 | 
            +
                    next if skip
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                    stat = File::Stat.new(file)
         | 
| 133 | 
            +
                    @files[file] = {
         | 
| 134 | 
            +
                      :size => 0,
         | 
| 135 | 
            +
                      :inode => [stat.ino, stat.dev_major, stat.dev_minor],
         | 
| 136 | 
            +
                      :create_sent => false,
         | 
| 137 | 
            +
                    }
         | 
| 138 | 
            +
                    if initial
         | 
| 139 | 
            +
                      @files[file][:initial] = true
         | 
| 140 | 
            +
                    end
         | 
| 141 | 
            +
                  end
         | 
| 142 | 
            +
                end # def _discover_file
         | 
| 143 | 
            +
             | 
| 144 | 
            +
              end # class Watch
         | 
| 145 | 
            +
            end # module FileWatch
         |