summaryrefslogtreecommitdiff
path: root/lib/polyjuice/client.ex
diff options
context:
space:
mode:
authorHubert Chathi <hubert@uhoreg.ca>2020-08-21 19:03:00 -0400
committerHubert Chathi <hubert@uhoreg.ca>2020-08-21 19:03:00 -0400
commit062b89962018f88c9390d7ba0822a99c68756c94 (patch)
tree982cbd9b21194284110c367a65f76890e9a072cc /lib/polyjuice/client.ex
parentadd lower-level client, acting like the old client (diff)
automatically start sync process
Diffstat (limited to 'lib/polyjuice/client.ex')
-rw-r--r--lib/polyjuice/client.ex183
1 files changed, 99 insertions, 84 deletions
diff --git a/lib/polyjuice/client.ex b/lib/polyjuice/client.ex
index 81415d1..2a15d28 100644
--- a/lib/polyjuice/client.ex
+++ b/lib/polyjuice/client.ex
@@ -16,8 +16,8 @@ defmodule Polyjuice.Client do
@moduledoc """
Matrix client functions.
- To create a client, use `start/2`, and to destroy it, use
- `stop/1`.
+ To start a client, use `start_link/2`, and to stop it, use
+ `stop/3`.
The struct in this module, or any struct that implements the
`Polyjuice.Client.API` protocol, can be used to connect to a Matrix server
@@ -28,8 +28,8 @@ defmodule Polyjuice.Client do
- `Polyjuice.Client.Media`: use the media repository
- `Polyjuice.Client.MsgBuilder`: build message contents
- To sync with the homeserver, start a process using the child spec returned by
- `Polyjuice.Client.API.sync_child_spec/3`.
+ The client defined in this module should work for most cases. If you want
+ more control, you can use `Polyjuice.Client.LowLevel` instead.
"""
require Logger
@@ -37,25 +37,28 @@ defmodule Polyjuice.Client do
@typedoc """
Matrix client.
- This struct is created by `start/1`.
+ This struct is created by `start_link/2`.
"""
# - `base_url`: Required. The base URL for the homeserver.
- # - `pid`: PID for the agent that contains the state.
+ # - `id`: An ID for the client.
# - `storage`: Required for some endpoints and for sync. Storage for the client.
# - `test`: if the client is used for a unit test (converts POST requests to PUT,
# to make mod_esi happy)
@opaque t :: %__MODULE__{
base_url: String.t(),
- pid: pid,
+ id: integer,
storage: Polyjuice.Client.Storage.t(),
+ handler: Polyjuice.Client.Handler.t(),
test: boolean
}
- @enforce_keys [:base_url, :pid]
+ @enforce_keys [:base_url, :id]
defstruct [
:base_url,
- :pid,
+ :id,
:storage,
+ :handler,
+ sync: true,
test: false
]
@@ -67,57 +70,84 @@ defmodule Polyjuice.Client do
access token to use.
- `user_id`: (required by some endpoints) the ID of the user
- `device_id`: the device ID
- - `storage`: (required by sync) the storage backend to use (see
+ - `storage`: (required for sync) the storage backend to use (see
`Polyjuice.Client.Storage`)
+ - `handler`: (required for sync) an event handler (see `Polyjuice.Client.Handler`)
+ - `sync`: whether to start a sync process (defaults to true). The sync process
+ will not start if there is no `storage` or `handler` provided.
+ - `sync_filter`: the filter to use for the sync. Defaults to no filter.
"""
- @spec start(base_url :: String.t(), opts :: Keyword.t()) :: t()
- def start(base_url, opts \\ []) when is_binary(base_url) do
- {:ok, pid} =
- Agent.start(fn ->
- %{
- access_token: Keyword.get(opts, :access_token),
- user_id: Keyword.get(opts, :user_id),
- device_id: Keyword.get(opts, :device_id)
- }
- end)
+ @spec start_link(base_url :: String.t(), opts :: Keyword.t()) :: t()
+ 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()
+
+ client_id = Agent.get_and_update(Polyjuice.Client.ID, fn id -> {id, id + 1} end)
+
+ access_token = Keyword.get(opts, :access_token)
+ user_id = Keyword.get(opts, :user_id)
+ device_id = Keyword.get(opts, :device_id)
+ sync = Keyword.get(opts, :sync, true)
+ storage = Keyword.get(opts, :storage)
+ handler = Keyword.get(opts, :handler)
+
+ children = [
+ %{
+ id: Polyjuice.Client,
+ start:
+ {Agent, :start_link,
+ [
+ fn ->
+ %{
+ access_token: access_token,
+ user_id: user_id,
+ device_id: device_id
+ }
+ 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, _pid} =
+ Supervisor.start_link(children,
+ strategy: :rest_for_one,
+ name: process_name(client_id, :supervisor)
+ )
%__MODULE__{
- base_url:
- if(String.ends_with?(base_url, "/"), do: base_url, else: base_url <> "/")
- |> URI.parse(),
- pid: pid,
- storage: Keyword.get(opts, :storage),
+ base_url: base_url,
+ id: client_id,
+ storage: storage,
+ handler: handler,
+ sync: sync,
test: Keyword.get(opts, :test, false)
}
end
@doc """
- Start a client, linking the client process to the calling process.
-
- See `start/2`.
+ Stop a client.
"""
- @spec start_link(base_url :: String.t(), opts :: Keyword.t()) :: t()
- def start_link(base_url, opts \\ []) when is_binary(base_url) do
- {:ok, pid} =
- Agent.start_link(fn ->
- %{
- access_token: Keyword.get(opts, :access_token),
- user_id: Keyword.get(opts, :user_id)
- }
- end)
+ @spec stop(Polyjuice.Client.t(), reason :: term, timeout()) :: :ok
+ def stop(%__MODULE__{id: id}, reason \\ :normal, timeout \\ :infinity) do
+ Supervisor.stop(process_name(id, :supervisor), reason, timeout)
+ end
- %__MODULE__{
- base_url:
- if(String.ends_with?(base_url, "/"), do: base_url, else: base_url <> "/")
- |> URI.parse(),
- pid: pid,
- storage: Keyword.get(opts, :storage),
- test: Keyword.get(opts, :test, false)
- }
+ @doc false
+ def process_name(id, process) do
+ {:via, Registry, {Polyjuice.Client, {id, process}}}
end
- def stop(%__MODULE__{pid: pid}) do
- Process.exit(pid, :normal)
+ defp sync_child_spec(base_url, client_id, opts) do
+ %{
+ id: Polyjuice.Client.Sync,
+ start: {Task, :start_link, [Polyjuice.Client.Sync, :sync, [base_url, client_id, opts]]}
+ }
end
@doc "The r0 client URL prefix"
@@ -149,36 +179,10 @@ defmodule Polyjuice.Client do
"""
@spec transaction_id(client_api :: Polyjuice.Client.API.t()) :: String.t()
def transaction_id(client_api)
-
- @doc """
- Get the child spec for the sync process.
-
- `listener` will receive messages with the sync results. Messages include:
-
- - `{:connected}`: the process has connected to the homeserver
- - `{:disconnected}`: the process has been disconnected from the homeserver
- - `{:initial_sync_completed}`: the first sync has completed
- - `{:limited, room_id, prev_batch}`: a room's timeline has been limited.
- Previous messages can be fetched using the `prev_batch`
- - `{:message, room_id, event}`: a message event has been received
- - `{:state, room_id, event}`: a state event has been received
- - `{:invite, room_id, inviter, invite_state}`: the user was invited to a room
- - `{:left, room_id}`: the user left a room
-
- `opts` is a keyword list of options:
-
- - `filter:` (string or map) the filter to use with the sync
- """
- @spec sync_child_spec(
- client_api :: Polyjuice.Client.API.t(),
- listener :: pid(),
- opts :: list()
- ) :: map()
- def sync_child_spec(client_api, listener, opts \\ [])
end
defimpl Polyjuice.Client.API do
- def call(%{base_url: base_url, pid: pid, test: test}, endpoint) do
+ def call(%{base_url: base_url, id: id, test: test}, endpoint) do
%Polyjuice.Client.Endpoint.HttpSpec{
method: method,
headers: headers,
@@ -192,7 +196,9 @@ defmodule Polyjuice.Client do
access_token =
if auth_required do
- Agent.get(pid, fn %{access_token: access_token} -> access_token end)
+ Agent.get(Polyjuice.Client.process_name(id, :state), fn %{access_token: access_token} ->
+ access_token
+ end)
else
nil
end
@@ -238,10 +244,6 @@ defmodule Polyjuice.Client do
def transaction_id(_) do
"#{Node.self()}_#{:erlang.system_time(:millisecond)}_#{:erlang.unique_integer()}"
end
-
- def sync_child_spec(client, listener, opts \\ []) do
- Polyjuice.Client.Sync.child_spec([client, listener | opts])
- end
end
@doc false
@@ -260,9 +262,9 @@ defmodule Polyjuice.Client do
@doc """
Synchronize messages from the server.
- Normally, you should create a sync process using
- `Polyjuice.Client.API.sync_child_spec/3` rather than calling this function, but
- this function may be used where more control is needed.
+ Normally, you should use `Polyjuice.Client`'s built-in sync process rather
+ than calling this function, but this function may be used where more control
+ is needed.
`opts` is a keyword list of options:
@@ -291,6 +293,7 @@ defmodule Polyjuice.Client do
)
end
+ @doc false
def make_login_identifier(identifier) do
case identifier do
x when is_binary(x) ->
@@ -353,10 +356,18 @@ defmodule Polyjuice.Client do
}
)
- Agent.cast(client.pid, fn state ->
+ Agent.cast(process_name(client.id, :state), fn state ->
%{state | access_token: access_token, user_id: user_id, device_id: device_id}
end)
+ if client.handler do
+ Polyjuice.Client.Handler.handle(
+ client.handler,
+ :logged_in,
+ {user_id, device_id, Map.drop(ret, ["user_id", "device_id"])}
+ )
+ end
+
ret
end
@@ -371,7 +382,11 @@ defmodule Polyjuice.Client do
%Polyjuice.Client.Endpoint.PostLogout{}
)
- Agent.cast(client.pid, fn state -> %{state | access_token: nil} end)
+ Agent.cast(process_name(client.id, :state), fn state -> %{state | access_token: nil} end)
+
+ if client.handler do
+ Polyjuice.Client.Handler.handle(client.handler, :logged_out)
+ end
{:ok}
end