class Build::Files::State

A stateful list of files captured at a specific time, which can then be checked for changes.

def self.dirty?(inputs, outputs)

@returns [Boolean] True if outputs need to be regenerated.
@parameter outputs [State] The output files state.
@parameter inputs [State] The input files state.
Check if outputs are dirty with respect to inputs.
def self.dirty?(inputs, outputs)
	outputs.dirty?(inputs)
end

def dirty?(inputs)

Are these (output) files dirty with respect to the given inputs?
def dirty?(inputs)
	if self.missing?
		return true
	end
	
	# If there are no inputs or no outputs, we are always clean:
	if inputs.empty? or self.empty?
		return false
	end
	
	oldest_output_time = self.oldest_time
	newest_input_time = inputs.newest_time
	
	if newest_input_time and oldest_output_time
		# We are dirty if any inputs are newer (bigger) than any outputs:
		if newest_input_time > oldest_output_time
			return true
		else
			return false
		end
	end
	
	return true
end

def empty?

@returns [Boolean] True if no files are being tracked.
Check if the state is empty.
def empty?
	@times.empty?
end

def initialize(files)

@raises [ArgumentError] If files is not a Files::List.
@parameter files [List] The list of files to track.
Initialize file state tracking.
def initialize(files)
	raise ArgumentError.new("Invalid files list: #{files}") unless Files::List === files
	
	@files = files
	
	@times = {}
	
	update!
end

def inspect

@returns [String] A debug string showing state changes.
Generate a string representation for debugging.
def inspect
	"<State Added:#{@added} Removed:#{@removed} Changed:#{@changed} Missing:#{@missing}>"
end

def missing?

@returns [Boolean] True if any files do not exist.
Check if any files are missing.
def missing?
	!@missing.empty?
end

def update!

@returns [Boolean] True if any files were added, changed, removed, or are missing.
Update the state by checking all files for changes.
def update!
	last_times = @times
	@times = {}
	
	@added = []
	@removed = []
	@changed = []
	@missing = []
	
	file_times = []
	
	@files.each do |path|
		# When processing the same path twice (perhaps by accident), we should skip it otherwise it might cause issues when being deleted from last_times multuple times.
		next if @times.include? path
		
		if File.exist?(path)
			modified_time = File.mtime(path)
			
			if last_time = last_times.delete(path)
				# Path was valid last update:
				if modified_time != last_time
					@changed << path
					
					# puts "Changed: #{path}"
				end
			else
				# Path didn't exist before:
				@added << path
				
				# puts "Added: #{path}"
			end
			
			@times[path] = modified_time
			
			unless File.directory?(path)
				file_times << FileTime.new(path, modified_time)
			end
		else
			@missing << path
			
			# puts "Missing: #{path}"
		end
	end
	
	@removed = last_times.keys
	# puts "Removed: #{@removed.inspect}" if @removed.size > 0
	
	@oldest_time = file_times.min
	@newest_time = file_times.max
	
	return @added.size > 0 || @changed.size > 0 || @removed.size > 0 || @missing.size > 0
end