diff options
author | Hubert Chathi <hubert@uhoreg.ca> | 2020-08-21 19:03:00 -0400 |
---|---|---|
committer | Hubert Chathi <hubert@uhoreg.ca> | 2020-08-21 19:03:00 -0400 |
commit | 062b89962018f88c9390d7ba0822a99c68756c94 (patch) | |
tree | 982cbd9b21194284110c367a65f76890e9a072cc /lib/polyjuice/client.ex | |
parent | add 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.ex | 183 |
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 |