lib/terjira/utils/file_cache.rb
require 'digest/md5'
require 'fileutils'
module Terjira
class FileCache
MAX_DEPTH = 32
ROOT_DIR = ENV['HOME'] ? "#{ENV['HOME']}/.terjira/" : '~/.terjira/'
class << self
def clear_all
return unless File.exist?(ROOT_DIR)
FileUtils.rm_r(ROOT_DIR)
FileUtils.mkdir_p(ROOT_DIR)
end
end
def initialize(domain, expiry = 0, depth = 2)
@domain = domain
@expiry = expiry
@depth = depth > MAX_DEPTH ? MAX_DEPTH : depth
FileUtils.mkdir_p(root_path)
end
# Set a cache value for the given key. If the cache contains an existing value for
# the key it will be overwritten.
def set(key, value)
f = File.open(get_path(key), 'w')
File.chmod(0600, f) unless File.stat(f).mode.to_s(8) =~ /600/
Marshal.dump(value, f)
f.close
end
# Return the value for the specified key from the cache. Returns nil if
# the value isn't found.
def get(key)
path = get_path(key)
if @expiry > 0 && File.exist?(path) && Time.new - File.new(path).mtime >= @expiry
FileUtils.rm(path)
end
return nil unless File.exist?(path)
result = nil
File.open(path, 'r') do |f|
result = Marshal.load(f)
end
result
end
# Return the value for the specified key from the cache if the key exists in the
# cache, otherwise set the value returned by the block. Returns the value if found
# or the value from calling the block that was set.
def fetch(key)
value = get(key)
return value if value
value = yield
set(key, value)
value
end
# Delete the value for the given key from the cache
def delete(key)
FileUtils.rm(get_path(key)) if File.exist? get_path(key)
end
# Delete ALL data from the cache, regardless of expiry time
def clear
return unless File.exist?(root_path)
FileUtils.rm_r(root_path)
FileUtils.mkdir_p(root_path)
end
# Delete all expired data from the cache
def purge
@t_purge = Time.new
purge_dir(root_path) if @expiry > 0
end
private
def get_path(key)
md5 = Digest::MD5.hexdigest(key.to_s).to_s
dir = File.join(root_path, md5.split(//)[0..@depth - 1])
FileUtils.mkdir_p(dir)
File.join(dir, md5)
end
def root_path
@root = File.join(ROOT_DIR, @domain) if @root.nil?
@root
end
def purge_dir(dir)
Dir.foreach(dir) do |f|
next if f =~ /^\.\.?$/
path = File.join(dir, f)
if File.directory?(path)
purge_dir(path)
elsif @t_purge - File.new(path).mtime >= @expiry
# Ignore files starting with . - we didn't create those
next if f =~ /^\./
FileUtils.rm(path)
end
end
# Delete empty directories
Dir.delete(dir) if Dir.entries(dir).delete_if { |e| e =~ /^\.\.?$/ }.empty?
end
end
end