aboutsummaryrefslogtreecommitdiff
path: root/lib/data
diff options
context:
space:
mode:
Diffstat (limited to 'lib/data')
-rw-r--r--lib/data/merklelist.ex117
1 files changed, 67 insertions, 50 deletions
diff --git a/lib/data/merklelist.ex b/lib/data/merklelist.ex
index 727f2a8..71483bd 100644
--- a/lib/data/merklelist.ex
+++ b/lib/data/merklelist.ex
@@ -1,50 +1,45 @@
defmodule SData.MerkleList do
- @moduledoc """
+ @moduledoc"""
A simple Merkle list store.
Future improvements:
- - When messages are inserted other than at the top, all intermediate hashes
- change. Keep track of the mapping from old hashes to new hashes so that get
- requests can work even for hashes that are not valid anymore.
- - group items in "pages" (bigger bundles)
+ - When messages are inserted other than at the top, all intermediate hashes
+ change. Keep track of the mapping from old hashes to new hashes so that get
+ requests can work even for hashes that are not valid anymore.
+ - group items in "pages" (bigger bundles)
"""
- use GenServer
+ defstruct [:root, :top, :cmp, :store]
- @doc """
- Start a Merkle List storage process.
+ @doc"""
+ Create a Merkle list store.
-
- `cmp` is a function that compares stored items and provides a total order.
- It must return:
- - `:after` if the first argument is more recent
- - `:duplicate` if the two items are the same
- - `:before` if the first argument is older
+ `cmp` is a function that compares stored items and provides a total order.
+ It must return:
+ - `:after` if the first argument is more recent
+ - `:duplicate` if the two items are the same
+ - `:before` if the first argument is older
"""
- def start_link(cmp) do
- GenServer.start_link(__MODULE__, cmp)
- end
-
- def init(cmp) do
+ def new(cmp) do
root_item = :root
root_hash = SData.term_hash root_item
- state = %{
+ state = %SData.MerkleList{
root: root_hash,
top: root_hash,
cmp: cmp,
store: %{ root_hash => root_item }
}
- {:ok, state}
+ state
end
- defp state_push(item, state) do
+ defp push(state, item) do
new_item = {item, state.top}
new_item_hash = SData.term_hash new_item
new_store = Map.put(state.store, new_item_hash, new_item)
%{ state | :top => new_item_hash, :store => new_store }
end
- defp state_pop(state) do
+ defp pop(state) do
if state.top == state.root do
:error
else
@@ -55,59 +50,81 @@ defmodule SData.MerkleList do
end
end
- defp insert_many(state, [], _callback) do
+ @doc"""
+ Insert a list of items in the store.
+
+ A callback function may be specified that is called on any item
+ that is sucessfully added, i.e. that wasn't present in the store before.
+ """
+ def insert_many(state, items, callback \\ (fn _ -> nil end)) do
+ items_sorted = Enum.sort(items, fn (x, y) -> state.cmp.(x, y) == :after end)
+ insert_many_aux(state, items_sorted, callback)
+ end
+
+ defp insert_many_aux(state, [], _callback) do
state
end
- defp insert_many(state, [item | rest], callback) do
- case state_pop(state) do
+ defp insert_many_aux(state, [item | rest], callback) do
+ case pop(state) do
:error ->
- new_state = state_push(item, insert_many(state, rest, callback))
+ new_state = push(insert_many_aux(state, rest, callback), item)
callback.(item)
new_state
{:ok, front, state_rest} ->
case state.cmp.(item, front) do
:after ->
- new_state = state_push(item, insert_many(state, rest, callback))
+ new_state = push(insert_many_aux(state, rest, callback), item)
callback.(item)
new_state
- :duplicate -> insert_many(state, rest, callback)
- :before -> state_push(front, insert_many(state_rest, [item | rest], callback))
+ :duplicate -> insert_many_aux(state, rest, callback)
+ :before -> push(insert_many_aux(state_rest, [item | rest], callback), item)
end
end
end
- def handle_cast({:insert, item}, state) do
- handle_cast({:insert_many, [item]}, state)
- end
+ @doc"""
+ Insert a single item in the store.
- def handle_cast({:insert_many, items}, state) do
- handle_cast({:insert_many, items, fn _ -> nil end}, state)
+ A callback function may be specified that is called on the item
+ if it is sucessfully added, i.e. it wasn't present in the store before.
+ """
+ def insert(state, item, callback \\ (fn _ -> nil end)) do
+ insert_many(state, [item], callback)
end
- def handle_cast({:insert_many, items, callback}, state) do
- items_sorted = Enum.sort(items, fn (x, y) -> state.cmp.(x, y) == :after end)
- new_state = insert_many(state, items_sorted, callback)
- {:noreply, new_state}
- end
+ @doc"""
+ Read some items from the state.
- def handle_call({:read, qbegin, qlimit}, _from, state) do
+ The two parameters are optional:
+ - qbegin : hash of the first item to read
+ - qlimit : number of items to read
+ """
+ def read(state, qbegin \\ nil, qlimit \\ nil) do
begin = qbegin || state.top
limit = qlimit || 20
- items = get_items_list(state, begin, limit)
- {:reply, items, state}
+ get_items_list(state, begin, limit)
end
- def handle_call(:top, _from, state) do
- {:reply, state.top, state}
+ @doc"""
+ Get the hash of the last item
+ """
+ def top(state) do
+ state.top
end
- def handle_call(:root, _from, state) do
- {:reply, state.root, state}
+ @doc"""
+ Get the hash of the root item
+ """
+ def root(state) do
+ state.root
end
- def handle_call({:has, hash}, _from, state) do
- {:reply, Map.has_key?(state.store, hash), state}
+ @doc"""
+ Check if the store holds a certain item
+ """
+ def has(state, hash) do
+ Map.has_key?(state.store, hash)
end
defp get_items_list(state, begin, limit) do
@@ -128,7 +145,7 @@ defmodule SData.MerkleList do
end
end
- @doc """
+ @doc"""
Compare function for timestamped strings
"""
def cmp_ts_str({ts1, str1}, {ts2, str2}) do