class Sprockets::Cache::FileStore


ActiveSupport::Cache::FileStore
See Also
environment.cache = Sprockets::Cache::FileStore.new(“/tmp”)
Assign the instance to the Environment#cache.
Public: A file system cache store that automatically cleans up old keys.

def self.default_logger

Returns a Logger.

Internal: Default standard error fatal logger.
def self.default_logger
  logger = Logger.new($stderr)
  logger.level = Logger::FATAL
  logger
end

def compute_size(caches)

def compute_size(caches)
  caches.inject(0) { |sum, (_, stat)| sum + stat.size }
end

def find_caches

mtime.
Returns an Array of [String filename, File::Stat] pairs sorted by

Internal: Get all cache files along with stats.
def find_caches
  Dir.glob(File.join(@root, '**/*.cache')).reduce([]) { |stats, filename|
    stat = safe_stat(filename)
    # stat maybe nil if file was removed between the time we called
    # dir.glob and the next stat
    stats << [filename, stat] if stat
    stats
  }.sort_by { |_, stat| stat.mtime.to_i }
end

def gc!

def gc!
  start_time = Time.now
  caches = find_caches
  size = compute_size(caches)
  delete_caches, keep_caches = caches.partition { |filename, stat|
    deleted = size > @gc_size
    size -= stat.size
    deleted
  }
  return if delete_caches.empty?
  FileUtils.remove(delete_caches.map(&:first), force: true)
  @size = compute_size(keep_caches)
  @logger.warn do
    secs = Time.now.to_f - start_time.to_f
    "#{self.class}[#{@root}] garbage collected " +
      "#{delete_caches.size} files (#{(secs * 1000).to_i}ms)"
  end
end

def get(key)

Returns Object or nil or the value is not set.

key - String cache key.

This API should not be used directly, but via the Cache wrapper API.

Public: Retrieve value from cache.
def get(key)
  path = File.join(@root, "#{key}.cache")
  value = safe_open(path) do |f|
    begin
      EncodingUtils.unmarshaled_deflated(f.read, Zlib::MAX_WBITS)
    rescue Exception => e
      @logger.error do
        "#{self.class}[#{path}] could not be unmarshaled: " +
          "#{e.class}: #{e.message}"
      end
      nil
    end
  end
  if value
    FileUtils.touch(path)
    value
  end
end

def initialize(root, max_size = DEFAULT_MAX_SIZE, logger = self.class.default_logger)

(default: 1000).
max_size - A Integer of the maximum number of keys the store will hold.
root - A String path to a directory to persist cached values to.

Public: Initialize the cache store.
def initialize(root, max_size = DEFAULT_MAX_SIZE, logger = self.class.default_logger)
  @root     = root
  @size     = find_caches.inject(0) { |n, (_, stat)| n + stat.size }
  @max_size = max_size
  @gc_size  = max_size * 0.75
  @logger   = logger
end

def inspect

Returns String.

Public: Pretty inspect
def inspect
  "#<#{self.class} size=#{@size}/#{@max_size}>"
end

def safe_open(path, &block)

def safe_open(path, &block)
  if File.exist?(path)
    File.open(path, 'rb', &block)
  end
rescue Errno::ENOENT
end

def safe_stat(fn)

def safe_stat(fn)
  File.stat(fn)
rescue Errno::ENOENT
  nil
end

def set(key, value)

Returns Object value.

value - Object value.
key - String cache key.

This API should not be used directly, but via the Cache wrapper API.

Public: Set a key and value in the cache.
def set(key, value)
  path = File.join(@root, "#{key}.cache")
  # Ensure directory exists
  FileUtils.mkdir_p File.dirname(path)
  # Check if cache exists before writing
  exists = File.exist?(path)
  # Serialize value
  marshaled = Marshal.dump(value)
  # Compress if larger than 4KB
  if marshaled.bytesize > 4 * 1024
    deflater = Zlib::Deflate.new(
      Zlib::BEST_COMPRESSION,
      Zlib::MAX_WBITS,
      Zlib::MAX_MEM_LEVEL,
      Zlib::DEFAULT_STRATEGY
    )
    deflater << marshaled
    raw = deflater.finish
  else
    raw = marshaled
  end
  # Write data
  PathUtils.atomic_write(path) do |f|
    f.write(raw)
    @size += f.size unless exists
  end
  # GC if necessary
  gc! if @size > @max_size
  value
end