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 @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 """ 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