floof/lib/floof/session_manager.ex
2022-11-26 15:34:51 +01:00

121 lines
3 KiB
Elixir

defmodule Floof.SessionManager do
use Agent
require Logger
require Record
# structure of data := %{dets file, subscription Map}
# dets data := {sesskey, hash} (via bag)
defstruct dets_file: "/var/spool/floof/sess.dets", subs: %{}
def start_link({file, opts}) do
Agent.start_link(fn ->
{:ok, file} = :dets.open_file(file, opts ++ [type: :bag])
%Floof.SessionManager{dets_file: file}
end, name: __MODULE__)
end
def attach(key, pid) do
modify_subscription(key, fn oldpid, dets_file ->
if oldpid != pid do
Floof.Distributor.unregister(pid)
if oldpid != nil do
Floof.Distributor.register(oldpid)
send(oldpid, {:SessionDetached, key})
end
end
case :dets.lookup(dets_file, key) do
[] -> nil
{:error, _} -> nil
_ -> send(pid, {:SessionPushed, key})
end
pid
end)
end
def detach(key, pid) do
modify_subscription(key, fn oldpid, _ ->
if oldpid == pid do
send(pid, {:SessionDetached, key})
nil
else
oldpid
end
end)
end
defp modify_subscription(key, handler) do
Agent.cast(__MODULE__, fn state ->
{_, subs2} =
Map.get_and_update(state.subs, key, fn oldpid ->
{nil, handler.(oldpid, state.dets_file)}
end)
%Floof.SessionManager{state | subs: subs2}
end)
end
def set_for_all(subkey, value, origin) do
Floof.PacketSpool.store(subkey, value)
Agent.cast(__MODULE__, fn state ->
filter_keys = MapSet.new(for {key, sub} <- state.subs do
if origin != sub and sub != nil do
send(sub, {:SessionPushed, key, subkey})
key
else
nil
end
end)
{:ok, all_keys} = all_session_keys(state)
added_dets_ents = for key <- MapSet.difference(all_keys, filter_keys), do: {key, subkey}
:ok = :dets.insert(state.dets_file, added_dets_ents)
state
end)
end
def set_soft(key, subkey) do
Agent.cast(__MODULE__, fn state ->
:ok = :dets.insert(state.dets_file, {key, subkey})
state
end)
end
def peek(key) do
Agent.get(
__MODULE__,
&case :dets.lookup(&1.dets_file, key) do
{:error, _} -> []
items -> Enum.map(items, fn {_, x} -> x end)
end
)
end
def drop(key, subkeys) do
Agent.cast(__MODULE__, fn state ->
dets_file = state.dets_file
:ok = :dets.delete_object(dets_file, Enum.map(subkeys, fn x -> {key, x} end))
# garbage collect unused objects
case :dets.match(dets_file, {'_', '$1'}) do
{:error, e} ->
Logger.error("garbage collection error: #{inspect(e)}")
keys ->
Floof.PacketSpool.keep_only(for [key] <- keys, do: key)
end
end)
end
defp all_session_keys(state) do
case :dets.select(state.dets_file, [{{'_', '_'}, [], ['$1']}]) do
{:error, x} -> {:error, x}
items -> {:ok, Enum.map(items, fn {x} -> x end)}
end
end
end