Packages

A simple content-addressable file system

Current section

Files

Jump to
hashfs lib hashfs.ex
Raw

lib/hashfs.ex

defmodule HashFS do
@moduledoc """
HashFS is a small library providing a content-addressable file system. This
is the documentation generated from the source.
"""
@readahead 10_000_000
@buffer_size 0xfff
defmacrop try!(clause) do
quote do
case unquote(clause) do
{:error, err} -> raise err
{:ok, val} -> val
end
end
end
defstruct [:path]
def new(path) do
File.mkdir_p(Path.join(path, "cache"))
File.mkdir_p(Path.join(path, "data"))
%HashFS{ path: path }
end
def has(fs, hash) do
hex = Base.encode16(hash, case: :lower)
path = Path.join([fs.path, "data", binary_part(hex, 0, 2), binary_part(hex, 2, byte_size(hex)-2)])
File.exists? path
end
@doc """
Get a new `File` with the contents corresponding to the calculated hash.
"""
def retrieve(fs, hash) do
hex = Base.encode16(hash, case: :lower)
path = Path.join([fs.path, "data", binary_part(hex, 0, 2), binary_part(hex, 2, byte_size(hex)-2)])
File.open path
end
@doc """
Same as `retrieve/2` but raises an error when something went wrong.
"""
def retrieve!(fs, path) do
try!(retrieve(fs, path))
end
@doc """
Store a file. The file's content is given by `input`, which is a `Stream`, and a `length` in bytes.
"""
def store(fs, input, length) do
sha = :crypto.hash_init(:sha)
sha = :crypto.hash_update(sha, "blob " <> to_string(length) <> "\0")
out_path = Path.join([fs.path, "cache", UUID.uuid4])
out = File.open!(out_path, [:write, :exclusive, :delayed_write])
hash = :crypto.hash_final(Enum.reduce(input, sha, fn(chunk, acc) ->
IO.binwrite(out, chunk)
:crypto.hash_update(acc, chunk)
end))
hex = Base.encode16(hash, case: :lower)
dir_path = Path.join([fs.path, "data", binary_part(hex, 0, 2)])
File.mkdir dir_path
File.close out
File.rename out_path, Path.join(dir_path, binary_part(hex, 2, byte_size(hex)-2))
{:ok,hash}
end
@doc """
Import a file into the repository by giving a reference to its location.
"""
def store(fs, path) do
info = try!(File.stat(path))
file = File.stream!(path, [read_ahead: @readahead], @buffer_size)
store(fs, file, info.size)
end
@doc """
The same as `store/3`, except that this raises an error when something went wrong.
"""
def store!(fs, input, length) do
try!(store(fs, input, length))
end
@doc """
Same as `store/2`, but raises an error when something went wrong.
"""
def store!(fs, path) do
try!(store(fs, path))
end
end