Packages
baobab
0.17.0
0.35.1
0.35.0
0.34.0
0.31.0
0.30.0
0.21.2
0.21.1
0.21.0
0.20.0
0.19.2
0.19.1
0.19.0
0.18.1
0.18.0
0.17.1
0.17.0
0.16.8
0.16.7
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.2
0.15.1
0.15.0
0.14.1
0.14.0
0.13.1
0.13.0
0.12.1
0.12.0
0.11.1
0.11.0
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.3
0.8.2
0.8.1
0.8.0
0.7.0
0.6.0
0.5.2
0.5.1
0.5.0
0.4.0
0.3.3
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.0
Bamboo append-only logs
Current section
Files
Jump to
Current section
Files
lib/baobab/interchange.ex
defmodule Baobab.Interchange do
alias Baobab.{Entry, Identity}
@moduledoc """
Functions related to the interchange of Bamboo data with
other Bamboo sources
"""
@doc """
Import and store a list of log entries from their binary format.
"""
@spec import_binaries([binary]) :: [%Baobab.Entry{} | {:error, String.t()}]
def import_binaries(binaries, options \\ [])
def import_binaries(binary, options) when is_binary(binary),
do: import_binaries([binary], options)
def import_binaries(binaries, options) when is_list(binaries) do
import_listed_binaries(binaries, options |> Baobab.optvals([:replace, :clump_id]), [])
end
def import_binaries(_, _), do: [{:error, "Import requires a list of binaries"}]
defp import_listed_binaries([], _, acc), do: acc
defp import_listed_binaries([binary | rest], {overwrite, clump_id} = opts, acc) do
result =
binary
|> Entry.from_binaries(true, clump_id)
|> Enum.map(fn e -> Entry.store(e, clump_id, overwrite) end)
import_listed_binaries(rest, opts, acc ++ result)
end
@doc """
Import a bamboo store
At present this only handles an exported directory as produced by `export_store/1`
"""
def import_store(path) do
# Right now we just mirror the export assuming everything
# is "in its place". Eventually we should have some switches
# to allow partial imports.. and from other sources
top = path |> Path.expand()
case Path.wildcard(Path.join([top, "identities/*.keyfile.json"])) do
[] -> notours()
ids -> import_store_identities(ids)
end
case Path.wildcard(Path.join([top, "content/*/*.bamboo.log"])) do
[] -> notours()
logs -> import_store_logs(logs)
end
# Adding this made old exports incompatible.
# Can be resolved with an empty `metadata.json` file if it comes up
case Path.wildcard(Path.join([top, "content/*/metadata.json"])) do
[] -> notours()
md -> import_store_metadata(md)
end
end
defp import_store_metadata([]), do: :ok
defp import_store_metadata([json_file | rest]) do
cid = clump_from_path(json_file)
case json_file |> File.read!() |> Jason.decode!() do
%{"blocks" => blist} -> perform_blocks(blist, cid)
_ -> notours()
end
import_store_metadata(rest)
end
defp perform_blocks([], _), do: :ok
defp perform_blocks([[a, l] | rest], cid) do
Baobab.ClumpMeta.block({a, l}, cid)
perform_blocks(rest, cid)
end
defp perform_blocks([i | rest], cid) when is_binary(i) or is_integer(i) do
Baobab.ClumpMeta.block(i, cid)
perform_blocks(rest, cid)
end
defp perform_blocks([_ | rest], cid), do: perform_blocks(rest, cid)
defp import_store_identities([]), do: :ok
defp import_store_identities([json_file | rest]) do
case json_file |> File.read!() |> Jason.decode!() do
# This is surprisingly liberal given our stance on current importing
%{"identity" => id, "secret_key" => sk, "public_key" => pk} ->
case Identity.create(id, sk) do
^pk -> :ok
_ -> notours()
end
_ ->
notours()
end
import_store_identities(rest)
end
defp import_store_logs([]), do: :ok
defp import_store_logs([bamboo_file | rest]) do
# The logs themselves contain all of the info for their structure
# except for the `clump_id` so we'll need that
cid = clump_from_path(bamboo_file)
bamboo_file
|> File.read!()
|> then(fn contents -> import_binaries([contents], replace: false, clump_id: cid) end)
import_store_logs(rest)
end
@doc """
Export full store contents to the provided directory
Produces:
- JSON keyfiles for each identity
- Per clump directories containing
- a file for each author, `log_id` pair
- a JSON metadata file
"""
def export_store(path) do
where = Path.expand(path)
id_path = Path.join([where, "identities"])
:ok = File.mkdir_p(id_path)
:ok = File.chmod(id_path, 0o700)
export_store_identities(Identity.list(), id_path)
bb_path = Path.join(where, "content")
:ok = File.mkdir_p(bb_path)
:ok = File.chmod(bb_path, 0o700)
export_store_clumps(Baobab.clumps(), bb_path)
where
end
defp export_store_identities([], _), do: :ok
defp export_store_identities([{i, pk} | rest], path) do
file = Path.join(path, i <> ".keyfile.json")
{:ok, json} =
%{
"source" => "baobab",
"key_encoding" => "base62",
"key_type" => "ed25519",
"identity" => i,
"public_key" => pk,
"secret_key" => Identity.key(i, :secret) |> BaseX.Base62.encode()
}
|> Jason.encode()
:ok = File.write(file, json)
:ok = File.chmod(file, 0o600)
export_store_identities(rest, path)
end
defp export_store_clumps([], _), do: :ok
defp export_store_clumps([cid | rest], path) do
export_store_clump(cid, path)
export_clump_metadata(cid, path)
export_store_clumps(rest, path)
end
defp export_clump_metadata(cid, path) do
file = Path.join([path, cid, "metadata.json"])
{:ok, json} =
%{"blocks" => Baobab.ClumpMeta.blocks_list(cid)}
|> Jason.encode()
:ok = File.write(file, json)
:ok = File.chmod(file, 0o600)
end
defp export_store_clump(cid, path) do
dir = Path.join(path, cid)
:ok = File.mkdir_p(dir)
export_store_logs(Baobab.stored_info(cid), cid, dir)
end
defp export_store_logs([], _, _), do: :ol
defp export_store_logs([{a, l, _} | rest], cid, dir) do
file = Path.join([dir, a <> "_" <> Integer.to_string(l) <> ".bamboo.log"])
log = Baobab.full_log(a, log_id: l, clump_id: cid, format: :binary) |> Enum.join("")
:ok = File.write(file, log)
:ok = File.chmod(file, 0o700)
export_store_logs(rest, cid, dir)
end
@doc false
def clump_from_path(path) do
[_, clump | _] = path |> Path.split() |> Enum.reverse()
clump
end
defp notours(), do: raise("Not a Baobab export structure")
end