defprotocol SData.Page do @moduledoc""" Protocol to be implemented by objects that are used as data pages in a pagestore and that may reference other data pages by their hash. """ @fallback_to_any true @doc""" Get hashes of all pages referenced by this page. """ def refs(page) end defimpl SData.Page, for: Any do def refs(_page), do: [] end defprotocol SData.PageStore do @moduledoc""" Protocol to be implemented for page stores to allow their manipulation. This protocol may also be implemented by store proxies that track operations and implement different synchronization or caching mechanisms. A page store is an object that stores data pages (arbitrary Erlang terms) and identifies them by their hash. Dependencies may exist between pages, in which case they form a Merkle DAG. """ @doc""" Put a page. Argument is the content of the page, returns the hash that the store has associated to it. Returns {hash, store} """ def put(store, page) @doc""" Get a page referenced by its hash. Returns page """ def get(store, hash) @doc""" Copy to the store a page and all its references from the other store. In the case of pages on the network in a distributed store, this may be lazy. Returns store """ def copy(store, other_store, hash) @doc""" Free a page referenced by its hash, marking it as no longer needed. Returns store """ def free(store, hash) end defmodule SData.LocalStore do @moduledoc""" A page store that saves all pages locally in RAM. The store is basically a dictionnary of hash to term mappings, which is mutated by put operations. """ defstruct [:pages] @doc""" Create empty LocalStore. """ def new() do %SData.LocalStore{ pages: %{} } end end defimpl SData.PageStore, for: SData.LocalStore do def put(store, page) do hash = SData.term_hash page store = %{ store | pages: Map.put(store.pages, hash, page) } { hash, store } end def get(store, hash) do store.pages[hash] end def copy(store, other_store, hash) do page = SData.PageStore.get(other_store, hash) refs = SData.Page.refs(page) store = Enum.reduce(refs, store, fn x, acc -> copy(acc, other_store, x) end) %{ store | pages: Map.put(store.pages, hash, page) } end def free(store, hash) do %{ store | pages: Map.delete(store.pages, hash) } end end