121 lines
3 KiB
Elixir
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
|