Skip to content
This repository
branch: master
file 175 lines (152 sloc) 4.879 kb
1 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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
require "logger"
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
  require "filewatch/winhelper"
end

module FileWatch
  class Watch
    attr_accessor :logger

    public
    def initialize(opts={})
      @iswindows = ((RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/) != nil)
      if opts[:logger]
        @logger = opts[:logger]
      else
        @logger = Logger.new(STDERR)
        @logger.level = Logger::INFO
      end
      @watching = []
      @exclude = []
      @files = Hash.new { |h, k| h[k] = Hash.new }
    end # def initialize

    public
    def logger=(logger)
      @logger = logger
    end

    public
    def exclude(path)
      path.to_a.each { |p| @exclude << p }
    end

    public
    def watch(path)
      if ! @watching.member?(path)
        @watching << path
        _discover_file(path, true)
      end

      return true
    end # def tail

    # Calls &block with params [event_type, path]
    # event_type can be one of:
    # :create_initial - initially present file (so start at end for tail)
    # :create - file is created (new file after initial globs, start at 0)
    # :modify - file is modified (size increases)
    # :delete - file is deleted
    public
    def each(&block)
      # Send any creates.
      @files.keys.each do |path|
        if ! @files[path][:create_sent]
          if @files[path][:initial]
            yield(:create_initial, path)
          else
            yield(:create, path)
          end
          @files[path][:create_sent] = true
        end
      end

      @files.keys.each do |path|
        begin
          stat = File::Stat.new(path)
        rescue Errno::ENOENT
          # file has gone away or we can't read it anymore.
          @files.delete(path)
          @logger.debug("#{path}: stat failed (#{$!}), deleting from @files")
          yield(:delete, path)
          next
        end

        if @iswindows
          fileId = Winhelper.GetWindowsUniqueFileIdentifier(path)
          inode = [fileId, stat.dev_major, stat.dev_minor]
        else
          inode = [stat.ino.to_s, stat.dev_major, stat.dev_minor]
        end

        if inode != @files[path][:inode]
          @logger.debug("#{path}: old inode was #{@files[path][:inode].inspect}, new is #{inode.inspect}")
          yield(:delete, path)
          yield(:create, path)
        elsif stat.size < @files[path][:size]
          @logger.debug("#{path}: file rolled, new size is #{stat.size}, old size #{@files[path][:size]}")
          yield(:delete, path)
          yield(:create, path)
        elsif stat.size > @files[path][:size]
          @logger.debug("#{path}: file grew, old size #{@files[path][:size]}, new size #{stat.size}")
          yield(:modify, path)
        end

        @files[path][:size] = stat.size
        @files[path][:inode] = inode
      end # @files.keys.each
    end # def each

    public
    def discover
      @watching.each do |path|
        _discover_file(path)
      end
    end

    public
    def subscribe(stat_interval = 1, discover_interval = 5, &block)
      glob = 0
      @quit = false
      while !@quit
        each(&block)

        glob += 1
        if glob == discover_interval
          discover
          glob = 0
        end

        sleep(stat_interval)
      end
    end # def subscribe

    private
    def _discover_file(path, initial=false)
      globbed_dirs = Dir.glob(path)
      @logger.debug("_discover_file_glob: #{path}: glob is: #{globbed_dirs}")
      if globbed_dirs.empty? && File.file?(path)
        globbed_dirs = [path]
        @logger.debug("_discover_file_glob: #{path}: glob is: #{globbed_dirs} because glob did not work")
      end
      globbed_dirs.each do |file|
        next if @files.member?(file)
        next unless File.file?(file)

        @logger.debug("_discover_file: #{path}: new: #{file} (exclude is #{@exclude.inspect})")

        skip = false
        @exclude.each do |pattern|
          if File.fnmatch?(pattern, File.basename(file))
            @logger.debug("_discover_file: #{file}: skipping because it " +
                          "matches exclude #{pattern}")
            skip = true
            break
          end
        end
        next if skip

        stat = File::Stat.new(file)
        @files[file] = {
          :size => 0,
          :inode => [stat.ino, stat.dev_major, stat.dev_minor],
          :create_sent => false,
        }

if @iswindows
          fileId = Winhelper.GetWindowsUniqueFileIdentifier(path)
          @files[file][:inode] = [fileId, stat.dev_major, stat.dev_minor]
        else
          @files[file][:inode] = [stat.ino.to_s, stat.dev_major, stat.dev_minor]
        end

        if initial
          @files[file][:initial] = true
        end
      end
    end # def _discover_file

    public
    def quit
      @quit = true
    end # def quit
  end # class Watch
end # module FileWatch
Something went wrong with that request. Please try again.