aboutsummaryrefslogtreecommitdiff
path: root/shard/lib/app/directory.ex
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2018-10-15 16:15:35 +0200
committerAlex Auvolat <alex@adnab.me>2018-10-15 16:15:35 +0200
commit181baf7e0c26c51d7c605bc9797f77ced9188455 (patch)
treeb8520b756c25df1648006f9390d51974b94ea9c1 /shard/lib/app/directory.ex
parent8c49dd71d29359447c24b1cd4f48a8faf0c4fdca (diff)
downloadshard-181baf7e0c26c51d7c605bc9797f77ced9188455.tar.gz
shard-181baf7e0c26c51d7c605bc9797f77ced9188455.zip
Basic infrastructure for dependency between shards
Diffstat (limited to 'shard/lib/app/directory.ex')
-rw-r--r--shard/lib/app/directory.ex168
1 files changed, 168 insertions, 0 deletions
diff --git a/shard/lib/app/directory.ex b/shard/lib/app/directory.ex
new file mode 100644
index 0000000..f7d871a
--- /dev/null
+++ b/shard/lib/app/directory.ex
@@ -0,0 +1,168 @@
+defmodule SApp.Directory do
+ @moduledoc"""
+ Shard application for a directory of other shards.
+
+ TODO: use MST for file list instead of plain list
+ """
+
+ use GenServer
+
+ require Logger
+
+ defmodule Manifest do
+ @moduledoc"""
+ Manifest for a directory. This directory is owned by a user,
+ has a name, and can be either public or private.
+ """
+
+ defstruct [:owner, :public, :name]
+
+ defimpl Shard.Manifest do
+ def module(_m), do: SApp.Directory
+ end
+ end
+
+ def start_link(manifest) do
+ GenServer.start_link(__MODULE__, manifest)
+ end
+
+ def init(manifest) do
+ %Manifest{owner: owner, public: public, name: name} = manifest
+ id = SData.term_hash manifest
+
+ Shard.Manager.dispatch_to(id, nil, self())
+ files = case Shard.Manager.load_state(id) do
+ nil ->
+ SData.SignRev.new %{}
+ st -> st
+ end
+ netgroup = case public do
+ true -> %SNet.PubShardGroup{id: id}
+ false -> %SNet.PrivGroup{pk_list: [owner]}
+ end
+ SNet.Group.init_lookup(netgroup, self())
+
+ {:ok, %{
+ owner: owner, public: public, name: name,
+ manifest: manifest, id: id, netgroup: netgroup,
+ files: files}}
+ end
+
+ def handle_call(:manifest, _from, state) do
+ {:reply, state.manifest, state}
+ end
+
+ def handle_call(:get_files, _from, state) do
+ {:reply, SData.SignRev.get(state.files), state}
+ end
+
+ def handle_call({:add_file, name, manifest}, _from, state) do
+ if Shard.Keys.have_sk?(state.owner) do
+ dict = SData.SignRev.get(state.files)
+ if dict[name] != nil and dict[name] != manifest do
+ {:reply, :exists_already, state}
+ else
+ dict = Map.put(dict, name, manifest)
+ GenServer.cast(Shard.Manager, {:dep_list, state.id, Map.values(dict)})
+ {:ok, st2} = SData.SignRev.set(state.files, dict, state.owner)
+ Shard.Manager.save_state(state.id, st2)
+ state = put_in(state.files, st2)
+ bcast_state(state)
+ {:reply, :ok, state}
+ end
+ else
+ {:reply, :impossible, state}
+ end
+ end
+
+ def handle_call({:rm_file, name}, _from, state) do
+ if Shard.Keys.have_sk?(state.owner) do
+ dict = SData.SignRev.get(state.files)
+ if dict[name] == nil do
+ {:reply, :not_found, state}
+ else
+ dict = Map.delete(dict, name)
+ GenServer.cast(Shard.Manager, {:dep_list, state.id, Map.values(dict)})
+ {:ok, st2} = SData.SignRev.set(state.files, dict, state.owner)
+ Shard.Manager.save_state(state.id, st2)
+ state = put_in(state.files, st2)
+ bcast_state(state)
+ {:reply, :ok, state}
+ end
+ else
+ {:reply, :impossible, state}
+ end
+ end
+
+ def handle_cast(:send_deps, state) do
+ dict = SData.SignRev.get(state.files)
+ GenServer.cast(Shard.Manager, {:dep_list, state.id, Map.values(dict)})
+ {:noreply, state}
+ end
+
+ def handle_cast({:interested, peer_pid, auth}, state) do
+ if SNet.Group.in_group?(state.netgroup, peer_pid, auth) do
+ SNet.Manager.send_pid(peer_pid, {state.id, nil, {:update, SData.SignRev.signed(state.files), true}})
+ end
+ {:noreply, state}
+ end
+
+ def handle_cast({:msg, conn_pid, auth, _shard_id, nil, msg}, state) do
+ if not SNet.Group.in_group?(state.netgroup, conn_pid, auth) do
+ {:noreply, state}
+ else
+ state = case msg do
+ {:update, signed, ask_reply} when signed != nil ->
+ state = case SData.SignRev.merge(state.files, signed, state.pk) do
+ {true, newfiles} ->
+ Shard.Manager.save_state(state.id, newfiles)
+ state = put_in(state.files, newfiles)
+ bcast_state(state, [conn_pid])
+ state
+ {false, _} ->
+ state
+ end
+ if ask_reply do
+ SNet.Manager.send_pid(conn_pid, {state.id, nil, {:update, SData.SignRev.signed(state.files), false}})
+ end
+ state
+ _ -> state
+ end
+ {:noreply, state}
+ end
+ end
+
+ defp bcast_state(state, exclude \\ []) do
+ msg = {state.id, nil, {:update, SData.SignRev.signed(state.files), false}}
+ SNet.Group.broadcast(state.netgroup, msg, exclude_pid: exclude)
+ end
+
+ # ================
+ # PUBLIC INTERFACE
+ # ================
+
+ @doc"""
+ Return list of files stored in this directory.
+
+ Returns a list of {name, manifests}.
+ """
+ def get_files(pid) do
+ GenServer.call(pid, :get_files)
+ end
+
+ @doc"""
+ Add a file to this directory. A file is a name for a shard manifest.
+ A file added to a directory becomes a dependency of the directory, i.e.
+ if the directory is pinned then all files inside are pinned as well.
+ """
+ def add_file(pid, name, manifest) do
+ GenServer.call(pid, {:add_file, name, manifest})
+ end
+
+ @doc"""
+ Remove a named file from this directory.
+ """
+ def rm_file(pid, name) do
+ GenServer.call(pid, {:rm_file, name})
+ end
+end