blob: d5dbc93e0ca95f8c66a5a06adbd23670446b89c9 [file] [log] [blame]
require 'digest/md5'
module Listen
class File
def self.change(record, rel_path)
path = Pathname.new(record.root) + rel_path
lstat = path.lstat
data = { mtime: lstat.mtime.to_f, mode: lstat.mode }
record_data = record.file_data(rel_path)
if record_data.empty?
record.update_file(rel_path, data)
return :added
end
if data[:mode] != record_data[:mode]
record.update_file(rel_path, data)
return :modified
end
if data[:mtime] != record_data[:mtime]
record.update_file(rel_path, data)
return :modified
end
return if /1|true/ =~ ENV['LISTEN_GEM_DISABLE_HASHING']
return unless 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)
#
return if data[:mtime].to_i + 2 <= Time.now.to_f
md5 = Digest::MD5.file(path).digest
record.update_file(rel_path, data.merge(md5: md5))
:modified if record_data[:md5] && md5 != record_data[:md5]
rescue SystemCallError
record.unset_path(rel_path)
:removed
rescue
Listen::Logger.debug "lstat failed for: #{rel_path} (#{$ERROR_INFO})"
raise
end
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
end
end