class DiskStore

Constants

DIR_FORMATTER
EXCLUDED_DIRS
FILENAME_MAX_SIZE
VERSION

Attributes

reaper[R]

Public Class Methods

new(path=nil, opts = {}) click to toggle source
# File lib/disk_store.rb, line 13
def initialize(path=nil, opts = {})
  path ||= "."
  @root_path = File.expand_path path
  @options = opts
  @reaper = Reaper.spawn_for(@root_path, @options)
end

Public Instance Methods

delete(key) click to toggle source
# File lib/disk_store.rb, line 56
def delete(key)
  file_path = key_file_path(key)
  if exist?(key)
    begin
      File.delete(file_path)
    rescue Error::ENOENT
      # Weirdness can happen with concurrency
    end
    
    delete_empty_directories(File.dirname(file_path))
  end
  true
end
exist?(key) click to toggle source
# File lib/disk_store.rb, line 52
def exist?(key)
  File.exist?(key_file_path(key))
end
fetch(key, md5 = nil) { || ... } click to toggle source
# File lib/disk_store.rb, line 70
def fetch(key, md5 = nil)
  if block_given?
    if exist?(key)
      read(key, md5)
    else
      io = yield
      write(key, io, md5)
      read(key)
    end
  else
    read(key, md5)
  end
end
read(key, md5 = nil) click to toggle source
# File lib/disk_store.rb, line 20
def read(key, md5 = nil)
  fd = File.open(key_file_path(key), 'rb')
  validate_file!(key_file_path(key), md5) if !md5.nil?
  fd
rescue MD5DidNotMatch => e
  delete(key)
  raise e
end
write(key, io, md5 = nil) click to toggle source
# File lib/disk_store.rb, line 29
def write(key, io, md5 = nil)
  file_path = key_file_path(key)
  ensure_cache_path(File.dirname(file_path))

  fd = File.open(file_path, 'wb') do |f|
    begin
      f.flock File::LOCK_EX
      IO::copy_stream(io, f)
    ensure
      # We need to make sure that any data written makes it to the disk.
      # http://stackoverflow.com/questions/6701103/understanding-ruby-and-os-i-o-buffering
      f.fsync
      f.flock File::LOCK_UN
    end
  end

  validate_file!(file_path, md5) if !md5.nil?
  fd
rescue MD5DidNotMatch => e
  delete(key)
  raise e
end

Private Instance Methods

delete_empty_directories(dir) click to toggle source

Delete empty directories in the cache.

# File lib/disk_store.rb, line 118
def delete_empty_directories(dir)
  return if File.realpath(dir) == File.realpath(@root_path)
  if Dir.entries(dir).reject{ |f| EXCLUDED_DIRS.include?(f) }.empty?
    Dir.delete(dir) rescue nil
    delete_empty_directories(File.dirname(dir))
  end
end
ensure_cache_path(path) click to toggle source

Make sure a file path’s directories exist.

# File lib/disk_store.rb, line 113
def ensure_cache_path(path)
  FileUtils.makedirs(path) unless File.exist?(path)
end
key_file_path(key) click to toggle source

Translate a key into a file path.

# File lib/disk_store.rb, line 89
def key_file_path(key)
  fname = URI.encode_www_form_component(key)
  hash = Zlib.adler32(fname)
  hash, dir_1 = hash.divmod(0x1000)
  dir_2 = hash.modulo(0x1000)
  fname_paths = []

  # Make sure file name doesn't exceed file system limits.
  begin
    fname_paths << fname[0, FILENAME_MAX_SIZE]
    fname = fname[FILENAME_MAX_SIZE..-1]
  end until fname.nil? || fname == ""

  File.join(@root_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths)
end
validate_file!(file_path, md5) click to toggle source
# File lib/disk_store.rb, line 105
def validate_file!(file_path, md5)
  real_md5 = Digest::MD5.file(file_path).hexdigest
  if md5 != real_md5
    raise MD5DidNotMatch.new("MD5 mismatch. Expected: #{md5}, Actual: #{real_md5}")
  end
end