class RunLoop::Directory
Class for performing operations on directories.
Public Class Methods
directory_digest(path, options={})
click to toggle source
Computes the digest of directory.
@param path A path to a directory. @param options Control the behavior of the method. @option options :handle_errors_by (:raising) Controls what to do when
File.read causes an error. The default behavior is to raise. Other options are: :logging and :ignoring. Logging will only happen if running in debug mode.
@raise ArgumentError When ‘path` is not a directory or path does not exist. @raise ArgumentError When options has n unsupported value.
# File lib/run_loop/directory.rb, line 33 def self.directory_digest(path, options={}) default_options = { :handle_errors_by => :raising } merged_options = default_options.merge(options) handle_errors_by = merged_options[:handle_errors_by] unless [:raising, :logging, :ignoring].include?(handle_errors_by) raise ArgumentError, %Q{Expected :handle_errors_by to be :raising, :logging, or :ignoring; found '#{handle_errors_by}' } end unless File.exist?(path) raise ArgumentError, "Expected '#{path}' to exist" end unless File.directory?(path) raise ArgumentError, "Expected '#{path}' to be a directory" end entries = self.recursive_glob_for_entries(path).sort if entries.empty? raise ArgumentError, "Expected a non-empty dir at '#{path}' found '#{entries}'" end debug = RunLoop::Environment.debug? file_shas = [] cumulative = OpenSSL::Digest::SHA256.new entries.each do |path| if !self.skip_file?(path, "SHA256", debug) begin file_sha = OpenSSL::Digest::SHA256.new contents = File.read(path, **{mode: "rb"}) file_sha << contents cumulative << contents file_shas << [file_sha.hexdigest] rescue => e case handle_errors_by when :logging message = %Q{RunLoop::Directory.directory_digest raised an error: #{e} while trying to find the SHA of this file: #{path} This is not a fatal error; it can be ignored. } RunLoop.log_debug(message) when :raising raise e.class, e.message when :ignoring # nop else # nop end end end end digest_of_digests = OpenSSL::Digest::SHA256.new digest_of_digests << file_shas.join("\n") # We have at least one example where the cumulative digest has an # unexpected value when computing the digest of an installed .app on an # iOS Simulator. I want return the cumulative.hexdigest in case there is # a client (end user) who is using this method. return digest_of_digests.hexdigest, cumulative.hexdigest end
recursive_glob_for_entries(base_dir)
click to toggle source
Dir.glob ignores files that start with ‘.’, but we often need to find dotted files and directories.
Ruby 2.* does the right thing by ignoring ‘..’ and ‘.’.
Ruby < 2.0 includes ‘..’ and ‘.’ in results which causes problems for some of run-loop’s internal methods. In particular ‘reset_app_sandbox`.
# File lib/run_loop/directory.rb, line 16 def self.recursive_glob_for_entries(base_dir) Dir.glob("#{base_dir}/{**/.*,**/*}").select do |entry| !(entry.end_with?('..') || entry.end_with?('.')) end end
size(path, format)
click to toggle source
# File lib/run_loop/directory.rb, line 107 def self.size(path, format) allowed_formats = [:bytes, :kb, :mb, :gb] unless allowed_formats.include?(format) raise ArgumentError, "Expected '#{format}' to be one of #{allowed_formats.join(', ')}" end unless File.exist?(path) raise ArgumentError, "Expected '#{path}' to exist" end unless File.directory?(path) raise ArgumentError, "Expected '#{path}' to be a directory" end entries = self.recursive_glob_for_entries(path) if entries.empty? raise ArgumentError, "Expected a non-empty dir at '#{path}' found '#{entries}'" end size = self.iterate_for_size(entries) case format when :bytes size when :kb size/1000.0 when :mb size/1000.0/1000.0 when :gb size/1000.0/1000.0/1000.0 else # Not expected to reach this. size end end
Private Class Methods
iterate_for_size(entries)
click to toggle source
# File lib/run_loop/directory.rb, line 184 def self.iterate_for_size(entries) debug = RunLoop::Environment.debug? size = 0 entries.each do |file| unless self.skip_file?(file, "SIZE", debug) begin size = size + File.size(file) rescue => e RunLoop.log_debug("Directory.iterate_for_size? rescued an ignorable error.") RunLoop.log_debug("#{e.class}: #{e.message}") end end end size end
skip_file?(file, task, debug)
click to toggle source
# File lib/run_loop/directory.rb, line 147 def self.skip_file?(file, task, debug) skip = false begin if File.directory?(file) # Skip directories skip = true elsif !Pathname.new(file).exist? # Skip broken symlinks skip = true elsif !File.exist?(file) # Skip files that don't exist skip = true else case File.ftype(file) when 'fifo' RunLoop.log_warn("#{task} IS SKIPPING FIFO #{file}") if debug skip = true when 'socket' RunLoop.log_warn("#{task} IS SKIPPING SOCKET #{file}") if debug skip = true when 'characterSpecial' RunLoop.log_warn("#{task} IS SKIPPING CHAR SPECIAL #{file}") if debug skip = true when 'blockSpecial' skip = true RunLoop.log_warn("#{task} SKIPPING BLOCK SPECIAL #{file}") if debug else end end rescue => e skip = true RunLoop.log_debug("Directory.skip_file? rescued an ignorable error.") RunLoop.log_debug("#{e.class}: #{e.message}") end skip end