summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHubert Chathi <hubert@uhoreg.ca>2020-09-16 00:06:13 -0400
committerHubert Chathi <hubert@uhoreg.ca>2020-09-16 00:06:13 -0400
commit76ba0ed6c0577f671ee7b9a865333c264d1985a0 (patch)
tree5cf6a3f6f6c366cbab42588944e6a8103aecd53a
parentmake Polyjuice.Client.start_link return {:ok, pid}, like a normal start_link (diff)
change structure of Polyjuice.Client process tree
-rw-r--r--lib/polyjuice/client.ex190
-rw-r--r--lib/polyjuice/client/sync.ex23
-rw-r--r--test/polyjuice/client/sync_test.exs8
-rw-r--r--test/polyjuice/client_test.exs40
4 files changed, 134 insertions, 127 deletions
diff --git a/lib/polyjuice/client.ex b/lib/polyjuice/client.ex
index 3f1668c..a39ac08 100644
--- a/lib/polyjuice/client.ex
+++ b/lib/polyjuice/client.ex
@@ -28,9 +28,10 @@ defmodule Polyjuice.Client do
The client defined in this module should work for most cases. If you want
more control, you can use `Polyjuice.Client.LowLevel` instead.
- To start a client with this module, create a process using `start_link/2`,
- and then call `get_client/1` to get a struct that can be used with the above
- modules. To stop the client, use `Polyjuice.Client.API.stop/3`.
+ To start a client with this module, create a process using `start/2` or
+ `start_link/2`, and then call `get_client/1` to get a struct that can be used
+ with the above modules. To stop the client, use
+ `Polyjuice.Client.API.stop/3`.
"""
@@ -44,13 +45,15 @@ defmodule Polyjuice.Client do
Polyjuice.Client.child_spec(["http://localhost:8008", sync: false])
"""
- use Supervisor
+ use GenServer
require Logger
@typedoc """
Matrix client.
- This struct is created by `start_link/2`.
+ This struct can be obtained by calling `get_client/1` after the client
+ process has been started with `start/2` or `start_link/2`, and implements
+ `Polyjuice.Client.API` protocol.
"""
# - `base_url`: Required. The base URL for the homeserver.
# - `id`: An ID for the client.
@@ -60,6 +63,7 @@ defmodule Polyjuice.Client do
@opaque t :: %__MODULE__{
base_url: String.t(),
id: integer,
+ pid: pid,
storage: Polyjuice.Client.Storage.t(),
handler: Polyjuice.Client.Handler.t(),
opts: list,
@@ -71,6 +75,7 @@ defmodule Polyjuice.Client do
defstruct [
:base_url,
:id,
+ :pid,
:storage,
:handler,
sync: true,
@@ -95,28 +100,28 @@ defmodule Polyjuice.Client do
- `sync_filter`: the filter to use for the sync. Defaults to no filter.
"""
@spec start_link(base_url :: String.t(), opts :: Keyword.t()) :: {:ok, pid}
- def start_link(base_url, opts) when is_binary(base_url) and is_list(opts) do
- base_url =
- if(String.ends_with?(base_url, "/"), do: base_url, else: base_url <> "/")
- |> URI.parse()
+ def start_link(base_url, opts \\ []) when is_binary(base_url) and is_list(opts) do
+ GenServer.start_link(__MODULE__, [base_url, opts])
+ end
- client_id = Agent.get_and_update(Polyjuice.Client.ID, &{&1, &1 + 1})
+ @doc """
+ Start a client.
- Supervisor.start_link(
- __MODULE__,
- [client_id, base_url, opts],
- name: process_name(client_id, :supervisor)
- )
+ See `start_link/2`.
+ """
+ @spec start(base_url :: String.t(), opts :: Keyword.t()) :: {:ok, pid}
+ def start(base_url, opts \\ []) when is_binary(base_url) and is_list(opts) do
+ GenServer.start(__MODULE__, [base_url, opts])
end
- def start_link(base_url) when is_binary(base_url), do: start_link(base_url, [])
+ @impl GenServer
+ def init([base_url, opts]) do
+ base_url =
+ if(String.ends_with?(base_url, "/"), do: base_url, else: base_url <> "/")
+ |> URI.parse()
- def start_link([base_url | opts]) when is_binary(base_url) and is_list(opts) do
- start_link(base_url, opts)
- end
+ client_id = Agent.get_and_update(Polyjuice.Client.ID, &{&1, &1 + 1})
- @impl Supervisor
- def init([client_id, base_url, opts]) do
sync = Keyword.get(opts, :sync, true)
storage = Keyword.get(opts, :storage)
handler = Keyword.get(opts, :handler)
@@ -171,50 +176,81 @@ defmodule Polyjuice.Client do
x
end
- children = [
- %{
- id: Polyjuice.Client,
- start:
- {Agent, :start_link,
- [
- fn ->
- %{
- access_token: access_token,
- user_id: user_id,
- device_id: device_id,
- client: %__MODULE__{
- base_url: base_url,
- id: client_id,
- storage: storage,
- handler: handler,
- sync: sync,
- opts: opts,
- test: Keyword.get(opts, :test, false)
- }
- }
- end,
- [name: process_name(client_id, :state)]
- ]}
- }
- | if(sync and access_token != nil and handler != nil and storage != nil,
- do: [sync_child_spec(base_url, client_id, opts)],
- else: []
+ {:ok, supervisor} =
+ DynamicSupervisor.start_link(
+ strategy: :one_for_one,
+ name: process_name(client_id, :supervisor)
+ )
+
+ if sync and access_token != nil and handler != nil and storage != nil do
+ {:ok, _pid} =
+ DynamicSupervisor.start_child(
+ supervisor,
+ sync_child_spec(base_url, client_id, self(), opts)
)
- ]
+ end
- Supervisor.init(children, strategy: :rest_for_one)
+ {:ok,
+ %{
+ access_token: access_token,
+ user_id: user_id,
+ device_id: device_id,
+ supervisor: supervisor,
+ client: %__MODULE__{
+ base_url: base_url,
+ id: client_id,
+ pid: self(),
+ storage: storage,
+ handler: handler,
+ sync: sync,
+ opts: opts,
+ test: Keyword.get(opts, :test, false)
+ }
+ }}
+ end
+
+ @doc """
+ Returns a specification to start this under a supervisor.
+
+ `arg` is a list where the first element is the server's base URL, and the
+ remainder of the list are options as document in `start_link/2`.
+ """
+ def child_spec([base_url | opts]) do
+ %{
+ id: __MODULE__,
+ start: {__MODULE__, :start_link, [base_url, opts]},
+ restart: :transient
+ }
end
@doc """
Get a struct that implements `Polyjuice.Client.API` from the pid given by
- `start_link`.
+ `start/2` or `start_link/2`.
"""
def get_client(pid) do
- agent_pid =
- Supervisor.which_children(pid)
- |> Enum.find_value(fn {id, pid, _, _} -> if id == Polyjuice.Client, do: pid end)
+ GenServer.call(pid, :get_client)
+ end
+
+ @impl GenServer
+ def terminate(_reason, %{supervisor: supervisor}) do
+ DynamicSupervisor.stop(supervisor)
+ end
+
+ @impl GenServer
+ def handle_call(:get_client, _from, %{client: client} = state) do
+ {:reply, client, state}
+ end
+
+ @impl GenServer
+ def handle_call(:get_state, _from, state) do
+ {:reply, Map.take(state, [:access_token, :user_id, :device_id]), state}
+ end
- Agent.get(agent_pid, &Map.fetch!(&1, :client))
+ @impl GenServer
+ def handle_cast({:set, new_state}, state) do
+ Map.take(new_state, [:access_token, :user_id, :device_id])
+ |> (&Map.merge(state, &1)).()
+ |> (&{:noreply, &1}).()
end
@doc false
@@ -222,11 +258,11 @@ defmodule Polyjuice.Client do
{:via, Registry, {Polyjuice.Client, {id, process}}}
end
- defp sync_child_spec(base_url, client_id, opts) do
+ defp sync_child_spec(base_url, client_id, pid, opts) do
%{
id: Polyjuice.Client.Sync,
restart: :transient,
- start: {Task, :start_link, [Polyjuice.Client.Sync, :sync, [base_url, client_id, opts]]}
+ start: {Task, :start_link, [Polyjuice.Client.Sync, :sync, [base_url, client_id, pid, opts]]}
}
end
@@ -281,7 +317,7 @@ defmodule Polyjuice.Client do
defimpl Polyjuice.Client.API do
def call(
- %{base_url: base_url, id: id, test: test} = client,
+ %{base_url: base_url, id: id, pid: pid, test: test} = client,
endpoint
) do
%Polyjuice.Client.Endpoint.HttpSpec{
@@ -297,7 +333,7 @@ defmodule Polyjuice.Client do
access_token =
if auth_required do
- Agent.get(Polyjuice.Client.process_name(id, :state), &Map.get(&1, :access_token))
+ GenServer.call(pid, :get_state) |> Map.get(:access_token)
else
nil
end
@@ -351,7 +387,7 @@ defmodule Polyjuice.Client do
Polyjuice.Client.kill_sync(id)
Polyjuice.Client.set_logged_out(
- id,
+ pid,
client.storage,
client.handler,
Map.get(json, "soft_logout", false)
@@ -403,8 +439,8 @@ defmodule Polyjuice.Client do
"#{Node.self()}_#{:erlang.system_time(:millisecond)}_#{:erlang.unique_integer()}"
end
- def stop(%{id: id}, reason \\ :normal, timeout \\ :infinity) do
- Supervisor.stop(Polyjuice.Client.process_name(id, :supervisor), reason, timeout)
+ def stop(%{pid: pid}, reason \\ :normal, timeout \\ :infinity) do
+ GenServer.stop(pid, reason, timeout)
end
end
@@ -519,9 +555,9 @@ defmodule Polyjuice.Client do
{:ok,
%{"access_token" => access_token, "user_id" => user_id, "device_id" => device_id} =
http_ret} ->
- Agent.cast(
- process_name(client.id, :state),
- &%{&1 | access_token: access_token, user_id: user_id, device_id: device_id}
+ GenServer.cast(
+ client.pid,
+ {:set, %{access_token: access_token, user_id: user_id, device_id: device_id}}
)
if client.storage do
@@ -561,9 +597,9 @@ defmodule Polyjuice.Client do
supervisor_name = process_name(client.id, :supervisor)
- Supervisor.start_child(
+ DynamicSupervisor.start_child(
supervisor_name,
- sync_child_spec(client.base_url, client.id, client.opts)
+ sync_child_spec(client.base_url, client.id, client.pid, client.opts)
)
end
@@ -578,7 +614,7 @@ defmodule Polyjuice.Client do
Log out an existing session.
"""
@spec log_out(client :: Polyjuice.Client.t()) :: {:ok} | any
- def log_out(%Polyjuice.Client{id: id, storage: storage, handler: handler} = client) do
+ def log_out(%Polyjuice.Client{id: id, pid: pid, storage: storage, handler: handler} = client) do
kill_sync(id)
case Polyjuice.Client.API.call(
@@ -586,7 +622,7 @@ defmodule Polyjuice.Client do
%Polyjuice.Client.Endpoint.PostLogout{}
) do
{:ok} ->
- set_logged_out(id, storage, handler, false)
+ set_logged_out(pid, storage, handler, false)
if storage do
Polyjuice.Client.Storage.kv_del(storage, "ca.uhoreg.polyjuice", "user_id")
@@ -603,13 +639,19 @@ defmodule Polyjuice.Client do
@doc false
def kill_sync(id) do
supervisor_name = process_name(id, :supervisor)
- Supervisor.terminate_child(supervisor_name, Polyjuice.Client.Sync)
- Supervisor.delete_child(supervisor_name, Polyjuice.Client.Sync)
+
+ case DynamicSupervisor.which_children(supervisor_name) do
+ [{_, pid, _, _}] when is_pid(pid) ->
+ DynamicSupervisor.terminate_child(supervisor_name, pid)
+
+ _ ->
+ nil
+ end
end
@doc false
- def set_logged_out(id, storage, handler, soft_logout) do
- Agent.cast(process_name(id, :state), &%{&1 | access_token: nil})
+ def set_logged_out(pid, storage, handler, soft_logout) do
+ GenServer.cast(pid, {:set, %{access_token: nil}})
if storage do
Polyjuice.Client.Storage.kv_del(storage, "ca.uhoreg.polyjuice", "access_token")
diff --git a/lib/polyjuice/client/sync.ex b/lib/polyjuice/client/sync.ex
index 3ac6f8a..573af71 100644
--- a/lib/polyjuice/client/sync.ex
+++ b/lib/polyjuice/client/sync.ex
@@ -26,6 +26,7 @@ defmodule Polyjuice.Client.Sync do
:handler,
:conn_ref,
:id,
+ :pid,
:homeserver_url,
:uri,
:storage,
@@ -45,6 +46,7 @@ defmodule Polyjuice.Client.Sync do
def sync(
homeserver_url,
id,
+ pid,
opts
) do
storage = Keyword.fetch!(opts, :storage)
@@ -92,6 +94,7 @@ defmodule Polyjuice.Client.Sync do
connect(%__MODULE__{
handler: handler,
id: id,
+ pid: pid,
homeserver_url: homeserver_url,
uri: uri,
query_params: query_params,
@@ -138,13 +141,7 @@ defmodule Polyjuice.Client.Sync do
e = &URI.encode_www_form/1
- {access_token, user_id} =
- Agent.get(
- Polyjuice.Client.process_name(state.id, :state),
- fn %{access_token: access_token, user_id: user_id} ->
- {access_token, user_id}
- end
- )
+ %{access_token: access_token, user_id: user_id} = GenServer.call(state.pid, :get_state)
path =
URI.merge(
@@ -202,7 +199,7 @@ defmodule Polyjuice.Client.Sync do
:hackney.close(state.conn_ref)
Polyjuice.Client.set_logged_out(
- state.id,
+ state.pid,
state.storage,
state.handler,
Map.get(json_body, "soft_logout", false)
@@ -245,11 +242,7 @@ defmodule Polyjuice.Client.Sync do
defp do_sync(state) do
if state.backoff, do: :timer.sleep(state.backoff * 1000)
- access_token =
- Agent.get(
- Polyjuice.Client.process_name(state.id, :state),
- fn %{access_token: access_token} -> access_token end
- )
+ %{access_token: access_token} = GenServer.call(state.pid, :get_state)
headers = [
{"Accept", "application/json"},
@@ -298,7 +291,7 @@ defmodule Polyjuice.Client.Sync do
:hackney.close(state.conn_ref)
Polyjuice.Client.set_logged_out(
- state.id,
+ state.pid,
state.storage,
state.handler,
Map.get(json_body, "soft_logout", false)
@@ -429,7 +422,7 @@ defmodule Polyjuice.Client.Sync do
end
)
- user_id = Agent.get(Polyjuice.Client.process_name(state.id, :state), &Map.get(&1, :user_id))
+ %{user_id: user_id} = GenServer.call(state.pid, :get_state)
inviter =
invite_state
diff --git a/test/polyjuice/client/sync_test.exs b/test/polyjuice/client/sync_test.exs
index e1085d6..94387aa 100644
--- a/test/polyjuice/client/sync_test.exs
+++ b/test/polyjuice/client/sync_test.exs
@@ -440,9 +440,7 @@ defmodule Polyjuice.Client.SyncTest do
Process.sleep(10)
assert Polyjuice.Client.process_name(client.id, :supervisor)
- |> Supervisor.which_children()
- |> Enum.find_value(fn {id, pid, _, _} -> if id == Polyjuice.Client.Sync, do: pid end) ==
- :undefined
+ |> DynamicSupervisor.which_children() == []
Polyjuice.Client.API.stop(client)
@@ -495,9 +493,7 @@ defmodule Polyjuice.Client.SyncTest do
Process.sleep(10)
assert Polyjuice.Client.process_name(client.id, :supervisor)
- |> Supervisor.which_children()
- |> Enum.find_value(fn {id, pid, _, _} -> if id == Polyjuice.Client.Sync, do: pid end) ==
- :undefined
+ |> DynamicSupervisor.which_children() == []
Polyjuice.Client.API.stop(client)
diff --git a/test/polyjuice/client_test.exs b/test/polyjuice/client_test.exs
index d9f95a8..96dc829 100644
--- a/test/polyjuice/client_test.exs
+++ b/test/polyjuice/client_test.exs
@@ -235,10 +235,7 @@ defmodule Polyjuice.ClientTest do
Polyjuice.Client.log_in_with_password(client, "@alice:example.org", "password")
- assert Agent.get(
- Polyjuice.Client.process_name(client.id, :state),
- &Map.take(&1, [:access_token, :user_id, :device_id])
- ) == %{
+ assert GenServer.call(client.pid, :get_state) == %{
access_token: "m.id.user_login",
user_id: "@alice:example.org",
device_id: "foo"
@@ -264,10 +261,7 @@ defmodule Polyjuice.ClientTest do
assert Polyjuice.Client.Storage.kv_get(storage, "ca.uhoreg.polyjuice", "user_id") == nil
assert Polyjuice.Client.Storage.kv_get(storage, "ca.uhoreg.polyjuice", "device_id") == nil
- assert Agent.get(
- Polyjuice.Client.process_name(client.id, :state),
- &Map.fetch!(&1, :access_token)
- ) == nil
+ assert GenServer.call(client.pid, :get_state) |> Map.get(:access_token) == nil
Polyjuice.Client.log_in_with_password(
client,
@@ -277,10 +271,7 @@ defmodule Polyjuice.ClientTest do
assert_receive({:polyjuice_client, :logged_in, {"@alice:example.org", "foo", _}})
- assert Agent.get(
- Polyjuice.Client.process_name(client.id, :state),
- &Map.take(&1, [:access_token, :user_id, :device_id])
- ) == %{
+ assert GenServer.call(client.pid, :get_state) == %{
access_token: "m.id.thirdparty_login",
user_id: "@alice:example.org",
device_id: "foo"
@@ -294,10 +285,7 @@ defmodule Polyjuice.ClientTest do
assert_receive({:polyjuice_client, :logged_in, {"@alice:example.org", "foo", _}})
- assert Agent.get(
- Polyjuice.Client.process_name(client.id, :state),
- &Map.take(&1, [:access_token, :user_id, :device_id])
- ) == %{
+ assert GenServer.call(client.pid, :get_state) == %{
access_token: "m.id.phone_login",
user_id: "@alice:example.org",
device_id: "foo"
@@ -313,10 +301,7 @@ defmodule Polyjuice.ClientTest do
assert_receive({:polyjuice_client, :logged_in, {"@alice:example.org", "foo", _}})
- assert Agent.get(
- Polyjuice.Client.process_name(client.id, :state),
- &Map.take(&1, [:access_token, :user_id, :device_id])
- ) == %{
+ assert GenServer.call(client.pid, :get_state) == %{
access_token: "ca.uhoreg.foo_login",
user_id: "@alice:example.org",
device_id: "foo"
@@ -349,10 +334,7 @@ defmodule Polyjuice.ClientTest do
client = Polyjuice.Client.get_client(client_pid)
- assert Agent.get(
- Polyjuice.Client.process_name(client.id, :state),
- &Map.take(&1, [:access_token, :user_id, :device_id])
- ) == %{
+ assert GenServer.call(client.pid, :get_state) == %{
access_token: "an_access_token",
user_id: "@alice:example.org",
device_id: "a_device"
@@ -382,10 +364,7 @@ defmodule Polyjuice.ClientTest do
client2 = Polyjuice.Client.get_client(client2_pid)
- assert Agent.get(
- Polyjuice.Client.process_name(client2.id, :state),
- &Map.take(&1, [:access_token, :user_id, :device_id])
- ) == %{
+ assert GenServer.call(client2.pid, :get_state) == %{
access_token: "an_access_token",
user_id: "@alice:example.org",
device_id: "a_device"
@@ -431,10 +410,7 @@ defmodule Polyjuice.ClientTest do
Polyjuice.Client.Room.send_event(client, "!a_room:example.org", "m.room.message", %{})
- assert Agent.get(
- Polyjuice.Client.process_name(client.id, :state),
- &Map.fetch!(&1, :access_token)
- ) == nil
+ assert GenServer.call(client.pid, :get_state) |> Map.get(:state) == nil
assert Polyjuice.Client.Storage.kv_get(storage, "ca.uhoreg.polyjuice", "access_token") ==
nil