1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
|
require 'fileutils'
require 'English'
module Moneta
module Adapters
# Filesystem backend
# @api public
class File
include Defaults
include Config
supports :create, :increment, :each_key
config :dir, required: true
# @param [Hash] options
# @option options [String] :dir Directory where files will be stored
def initialize(options = {})
configure(**options)
FileUtils.mkpath(config.dir)
raise "#{config.dir} is not a directory" unless ::File.directory?(config.dir)
end
# (see Proxy#key?)
def key?(key, options = {})
::File.exist?(store_path(key))
end
# (see Proxy#each_key)
def each_key(&block)
entries = ::Dir.entries(config.dir).reject do |k|
::File.directory?(::File.join(config.dir, k))
end
if block_given?
entries.each { |k| yield(k) }
self
else
enum_for(:each_key) { ::Dir.entries(config.dir).length - 2 }
end
end
# (see Proxy#load)
def load(key, options = {})
::File.read(store_path(key), mode: 'rb')
rescue Errno::ENOENT
nil
end
# (see Proxy#store)
def store(key, value, options = {})
temp_file = ::File.join(config.dir, "value-#{$PROCESS_ID}-#{Thread.current.object_id}")
path = store_path(key)
FileUtils.mkpath(::File.dirname(path))
::File.open(temp_file, 'wb') { |f| f.write(value) }
::File.rename(temp_file, path)
value
rescue
File.unlink(temp_file) rescue nil
raise
end
# (see Proxy#delete)
def delete(key, options = {})
value = load(key, options)
::File.unlink(store_path(key))
value
rescue Errno::ENOENT
nil
end
# (see Proxy#clear)
def clear(options = {})
temp_dir = "#{config.dir}-#{$PROCESS_ID}-#{Thread.current.object_id}"
::File.rename(config.dir, temp_dir)
FileUtils.mkpath(config.dir)
self
rescue Errno::ENOENT
self
ensure
FileUtils.rm_rf(temp_dir)
end
# (see Proxy#increment)
def increment(key, amount = 1, options = {})
path = store_path(key)
FileUtils.mkpath(::File.dirname(path))
::File.open(path, ::File::RDWR | ::File::CREAT) do |f|
Thread.pass until f.flock(::File::LOCK_EX)
content = f.read
amount += Integer(content) unless content.empty?
content = amount.to_s
f.binmode
f.pos = 0
f.write(content)
f.truncate(content.bytesize)
amount
end
end
# HACK: The implementation using File::EXCL is not atomic under JRuby 1.7.4
# See https://github.com/jruby/jruby/issues/827
if defined?(JRUBY_VERSION)
# (see Proxy#create)
def create(key, value, options = {})
path = store_path(key)
FileUtils.mkpath(::File.dirname(path))
# Call native java.io.File#createNewFile
return false unless ::Java::JavaIo::File.new(path).createNewFile
::File.open(path, 'wb+') { |f| f.write(value) }
true
end
else
# (see Proxy#create)
def create(key, value, options = {})
path = store_path(key)
FileUtils.mkpath(::File.dirname(path))
::File.open(path, ::File::WRONLY | ::File::CREAT | ::File::EXCL) do |f|
f.binmode
f.write(value)
end
true
rescue Errno::EEXIST
false
end
end
protected
def store_path(key)
::File.join(config.dir, key)
end
end
end
end
|