class Hiera
class Filecache
def initialize
@cache = {}
end
# Reads a file, optionally parse it in some way check the
# output type and set a default
#
# Simply invoking it this way will return the file contents
#
# data = read("/some/file")
#
# But as most cases of file reading in hiera involves some kind
# of parsing through a serializer there's some help for those
# cases:
#
# data = read("/some/file", Hash, {}) do |data|
# JSON.parse(data)
# end
#
# In this case it will read the file, parse it using JSON then
# check that the end result is a Hash, if it's not a hash or if
# reading/parsing fails it will return {} instead
#
# Prior to calling this method you should be sure the file exist
def read(path, expected_type = Object, default=nil, &block)
read_file(path, expected_type, &block)
rescue TypeError => detail
Hiera.debug("#{detail.message}, setting defaults")
@cache[path][:data] = default
rescue => detail
error = "Reading data from #{path} failed: #{detail.class}: #{detail}"
if default.nil?
raise detail
else
Hiera.debug(error)
@cache[path][:data] = default
end
end
# Read a file when it changes. If a file is re-read and has not changed since the last time
# then the last, processed, contents will be returned.
#
# The processed data can also be checked against an expected type. If the
# type does not match a TypeError is raised.
#
# No error handling is done inside this method. Any failed reads or errors
# in processing will be propagated to the caller
def read_file(path, expected_type = Object)
if stale?(path)
data = File.read(path, :encoding => 'BOM|UTF-8')
@cache[path][:data] = block_given? ? yield(data) : data
if !@cache[path][:data].is_a?(expected_type)
raise TypeError, "Data retrieved from #{path} is #{@cache[path][:data].class} not #{expected_type}"
end
end
@cache[path][:data]
end
private
def stale?(path)
meta = path_metadata(path)
@cache[path] ||= {:data => nil, :meta => nil}
if @cache[path][:meta] == meta
return false
else
@cache[path][:meta] = meta
return true
end
end
# This is based on the old caching in the YAML backend and has a
# resolution of 1 second, changes made within the same second of
# a previous read will be ignored
def path_metadata(path)
stat = File.stat(path)
{:inode => stat.ino, :mtime => stat.mtime, :size => stat.size}
end
end
end
Copyright 2K16 - 2K18 Indonesian Hacker Rulez