From 181baf7e0c26c51d7c605bc9797f77ced9188455 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Mon, 15 Oct 2018 16:15:35 +0200 Subject: Basic infrastructure for dependency between shards --- shard/lib/app/directory.ex | 168 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 shard/lib/app/directory.ex (limited to 'shard/lib/app/directory.ex') 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 -- cgit v1.2.3