Class: Listen::File

Inherits:
Object
  • Object
show all
Defined in:
lib/listen/file.rb

Class Method Summary collapse

Class Method Details

.change(record, dir, rel_path) ⇒ Object



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
# File 'lib/listen/file.rb', line 3

def self.change(record, dir, rel_path)
  path = dir + rel_path
  lstat = path.lstat

  data = { mtime: lstat.mtime.to_f, mode: lstat.mode }

  record_data = record.file_data(dir, rel_path)

  if record_data.empty?
    record.async.update_file(dir, rel_path, data)
    return :added
  end

  if data[:mode] != record_data[:mode]
    record.async.update_file(dir, rel_path, data)
    return :modified
  end

  if data[:mtime] != record_data[:mtime]
    record.async.update_file(dir, rel_path, data)
    return :modified
  end

  unless /1|true/ =~ ENV['LISTEN_GEM_DISABLE_HASHING']
    if self.inaccurate_mac_time?(lstat)
      # Check if change happened within 1 second (maybe it's even
      # too much, e.g. 0.3-0.5 could be sufficient).
      #
      # With rb-fsevent, there's a (configurable) latency between
      # when file was changed and when the event was triggered.
      #
      # If a file is saved at ???14.998, by the time the event is
      # actually received by Listen, the time could already be e.g.
      # ???15.7.
      #
      # And since Darwin adapter uses directory scanning, the file
      # mtime may be the same (e.g. file was changed at ???14.001,
      # then at ???14.998, but the fstat time would be ???14.0 in
      # both cases).
      #
      # If change happend at ???14.999997, the mtime is 14.0, so for
      # an mtime=???14.0 we assume it could even be almost ???15.0
      #
      # So if Time.now.to_f is ???15.999998 and stat reports mtime
      # at ???14.0, then event was due to that file'd change when:
      #
      # ???15.999997 - ???14.999998 < 1.0s
      #
      # So the "2" is "1 + 1" (1s to cover rb-fsevent latency +
      # 1s maximum difference between real mtime and that recorded
      # in the file system)
      #
      if data[:mtime].to_i + 2 > Time.now.to_f
        begin
          md5 = Digest::MD5.file(path).digest
          record.async.update_file(dir, rel_path, data.merge(md5: md5))
          :modified if record_data[:md5] && md5 != record_data[:md5]

        rescue SystemCallError
          # ignore failed md5
        end
      end
    end
  end
rescue SystemCallError
  record.async.unset_path(dir, rel_path)
  :removed
rescue
  Celluloid::Logger.debug "lstat failed for: #{rel_path} (#{$!})"
  raise
end

.inaccurate_mac_time?(stat) ⇒ Boolean

Returns:

  • (Boolean)


75
76
77
78
79
80
81
82
# File 'lib/listen/file.rb', line 75

def self.inaccurate_mac_time?(stat)
  # 'mac' means Modified/Accessed/Created

  # Since precision depends on mounted FS (e.g. you can have a FAT partiion
  # mounted on Linux), check for fields with a remainder to detect this

  [stat.mtime, stat.ctime, stat.atime].map(&:usec).all?(&:zero?)
end