76 lines
2 KiB
Ruby
76 lines
2 KiB
Ruby
|
# frozen_string_literal: true
|
||
|
|
||
|
require 'digest'
|
||
|
|
||
|
module AceOfBase
|
||
|
# Implements our database.
|
||
|
class Storage
|
||
|
include Enumerable
|
||
|
|
||
|
STORAGE_DIR = File.expand_path('../../data/', __dir__)
|
||
|
|
||
|
Error = Class.new(AceOfBase::Error)
|
||
|
|
||
|
def initialize(storage_dir = STORAGE_DIR)
|
||
|
@storage_dir = storage_dir
|
||
|
ensure_data_dir_exists_and_is_writable!
|
||
|
end
|
||
|
|
||
|
def store(record)
|
||
|
with_write_locked_file_for_record(record) do |file|
|
||
|
file.write(RecordSerialiser.encode(record))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def retrieve(project, shot, version)
|
||
|
with_read_locked_file(hash_for(project, shot, version)) do |file|
|
||
|
RecordSerialiser.decode(file.read)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def each
|
||
|
Dir['*', base: storage_dir].each do |path|
|
||
|
with_read_locked_file(path) do |file|
|
||
|
yield RecordSerialiser.decode(file.read)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
attr_reader :storage_dir
|
||
|
|
||
|
def ensure_data_dir_exists_and_is_writable!
|
||
|
raise Error, format('Storage directory (%p) does not exist.', storage_dir) unless File.directory?(storage_dir)
|
||
|
raise Error, format('Storage directory (%p) is not writable.', storage_dir) unless File.writable?(storage_dir)
|
||
|
end
|
||
|
|
||
|
def with_write_locked_file_for_record(record)
|
||
|
target = File.join(storage_dir, hash_for(record.project, record.shot, record.version))
|
||
|
File.open(target, File::CREAT | File::WRONLY, 0o600) do |file|
|
||
|
raise Error, 'Unable to acquire exclusive lock.' unless file.flock(File::LOCK_EX | File::LOCK_NB)
|
||
|
|
||
|
result = yield file
|
||
|
file.flock(File::LOCK_UN)
|
||
|
result
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def with_read_locked_file(path)
|
||
|
target = File.join(storage_dir, path)
|
||
|
File.open(target, File::RDONLY) do |file|
|
||
|
file.flock(File::LOCK_SH)
|
||
|
result = yield file
|
||
|
file.flock(File::LOCK_UN)
|
||
|
result
|
||
|
end
|
||
|
rescue Errno::ENOENT
|
||
|
raise Error, 'Record not found'
|
||
|
end
|
||
|
|
||
|
def hash_for(project, shot, version)
|
||
|
Digest::SHA1.hexdigest("#{project}|#{shot}|#{version}")
|
||
|
end
|
||
|
end
|
||
|
end
|