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