diff options
Diffstat (limited to 'lib/data')
-rw-r--r-- | lib/data/merklelist.ex | 117 |
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 |