Search code examples
rubymacosfiledirectory

Has Directory Content Changed?


How can I check a directory to see if its contents has changed since a given point in time?

I don't need to be informed when it changes, or what has changed. I just need a way to check if it has changed.


Solution

  • If you just need to know if names have changed or files have been added/removed, you can try this:

    Dir.glob('some_directory/**/*').hash
    

    Just store and compare the hash values. You can obviously go further by getting more information out of a call to ls, for example, or out of File objects that represent each of the files in your directory structure, and hashing that.

    Dir.glob('some_directory/**/*').map { |name| [name, File.mtime(name)] }.hash
    

    UM ACTUALLY I'm being dumb and hash is only consistent for any one runtime environment of ruby. Let's use the standard Zlib::crc32 instead, e.g.

    Zlib::crc32(Dir.glob('some_directory/**/*').map { |name| [name, File.mtime(name)] }.to_s)
    

    My concern is that this approach will be memory-hungry and slow if you're checking a very large filesystem. Perhaps globbing the entire structure and mapping it isn't the way--if you have a lot of subdirectories you could walk them recursively and calculate a checksum for each, then combine the checksums.

    This might be better for larger directories:

    Dir.glob('some_directory/**/*').map do |name| 
      s = [name, File.mtime(name)].to_s
      [Zlib::crc32(s), s.length]
    end.inject(Zlib::crc32('')) do |combined, x| 
      Zlib::crc32_combine(combined, x[0], x[1])
    end
    

    This would be less prone to collisions:

    Dir.glob('some_directory/**/*').map do |name| 
      [name, File.mtime(name)].to_s
    end.inject(Digest::SHA512.new) do |digest, x| 
      digest.update x
    end.to_s