matrix-glib-sdk/src/matrix-client.c

497 lines
18 KiB
C
Raw Permalink Normal View History

2017-11-29 08:46:49 +00:00
/*
* This file is part of matrix-glib-sdk
*
* matrix-glib-sdk is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either
* version 3 of the License, or (at your option) any later version.
*
* matrix-glib-sdk is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with matrix-glib-sdk. If not, see
* <http://www.gnu.org/licenses/>.
*/
#include "matrix-client.h"
#include "matrix-marshalers.h"
/**
* SECTION:matrix-client
* @short_description: Interface for Matrix client implementations
* @title: Interface for Matrix client implementations
*/
enum {
SIGNAL_LOGIN_FINISHED,
SIGNAL_EVENT,
SIGNAL_POLLING_STARTED,
SIGNAL_POLLING_STOPPED,
NUM_SIGNALS
};
static guint matrix_client_signals[NUM_SIGNALS] = {0};
/**
* MatrixClientInterface:
* @login_with_password: the virtual function pointer for matrix_client_login_with_password()
* @register_with_password: the virtual function pointer for matrix_client_register_with_password()
* @logout: the virtual function pointer for matrix_client_logout()
* @begin_polling: the virtual function pointer for matrix_client_begin_polling()
* @stop_polling: the virtual function pointer for matrix_client_stop_polling()
* @get_user_profile: the virtual function pointer for matrix_client_get_user_profile()
* @get_user_presence: the virtual function pointer for matrix_client_get_user_presence()
* @get_room_by_id: the virtual function pointer for matrix_client_get_room_by_id()
* @get_room_by_alias: the virtual function pointer for matrix_client_get_room_by_alias()
* @send: the virtual function pointer for matrix_client_send()
* @save_state: the virtual function pointer for matrix_client_save_state()
* @load_state: the virtual function pointer for matrix_client_load_state()
* @login_finished: the class closure for the #MatrixClient::login-finished signal
* @event: the class closure for the #MatrixClient::event signal
*
* Base interface for client communication with a Matrix.org homeserver
*/
G_DEFINE_INTERFACE(MatrixClient, matrix_client, G_TYPE_OBJECT);
/**
* MatrixClientSendCallback:
* @client: the #MatrixClient that initiated the request
* @event_id: the event ID of the event just sent
* @error: (nullable): a #GError holding errors that happened during sending, if any
* @user_data: (nullable): user data set when event sending was initiated
*
* Callback function type for matrix_client_send().
*/
/**
* MatrixClientEventCallback:
* @client: the #MatrixClient that sent the signal
* @room_id: the room ID from which the event originated
* @raw_event: the raw event
* @matrix_event: (nullable): a #MatrixMessageBase derived object
* @user_data: (nullable): user data set when the signal was connected
*
* Callback function type for matrix_client_connect_event().
*
* @matrix_event may be unset if there was no message handler registered for the message type
* (using matrix_message_register_type()), or if the message couldnt be deserialised by the
* message handler.
*/
/**
* matrix_client_login_with_password:
* @client: a #MatrixClient
* @username: the username to login with
* @password: the password to use
* @error: a #GError, or %NULL to ignore errors
*
* Authenticate with the Matrix.org server with a username and password.
*/
void
matrix_client_login_with_password(MatrixClient *matrix_client, const gchar *username, const gchar *password, GError **error)
{
g_return_if_fail(matrix_client != NULL);
MATRIX_CLIENT_GET_IFACE(matrix_client)->login_with_password(matrix_client, username, password, error);
}
/**
* matrix_client_register_with_password:
* @client: a #MatrixClient
* @username: the username to register. If omitted, the server will generate one
* @password: the password to use with the registration
* @error: a #GError, or %NULL to ignore errors
*
* Register @username with the homeserver as a normal user. Upon success, the user is registered
* and authenticated.
*
* Implementations must emit the #MatrixClient::login-finished signal when a response arrives.
*
* This method registers a normal user account. If you want to register a different kind of user,
* use matrix_api_register_account().
*/
void
matrix_client_register_with_password(MatrixClient *matrix_client, const gchar *username, const gchar *password, GError **error)
{
g_return_if_fail(matrix_client != NULL);
MATRIX_CLIENT_GET_IFACE(matrix_client)->register_with_password(matrix_client, username, password, error);
}
/**
* matrix_client_logout:
* @client: an object implementing the #MatrixClient interface
* @error: a #GError, or %NULL to ignore errors
*
* Logout from the homeserver. As Matrix.org doesnt have such a concept, this cancels all ongoing
* requests and clears the authentication data (e.g. tokens).
*/
void
matrix_client_logout(MatrixClient *matrix_client, GError **error)
{
g_return_if_fail(matrix_client != NULL);
MATRIX_CLIENT_GET_IFACE(matrix_client)->logout(matrix_client, error);
}
/**
* matrix_client_begin_polling:
* @client: an object implementing the #MatrixClient interface
* @error: a #GError, or %NULL to ignore errors
*
* Begin polling the event stream.
*/
void
matrix_client_begin_polling(MatrixClient *matrix_client, GError **error)
{
g_return_if_fail(matrix_client != NULL);
MATRIX_CLIENT_GET_IFACE(matrix_client)->begin_polling(matrix_client, error);
}
/**
* matrix_client_stop_polling:
* @client: a #MatrixClient
* @cancel_ongoing: if %TRUE, ongoing requests will be cancelled, too
* @error: a #GError, or %NULL to ignore errors
*
* Stop polling the event stream. If @cancel_ongoing is %TRUE, ongoing requests will be cancelled,
* too.
*/
void
matrix_client_stop_polling(MatrixClient *matrix_client, gboolean cancel_ongoing, GError **error)
{
g_return_if_fail(matrix_client != NULL);
MATRIX_CLIENT_GET_IFACE(matrix_client)->stop_polling(matrix_client, cancel_ongoing, error);
}
/**
* matrix_client_emit_login_finished:
* @client: a #MatrixClient
* @success: set to %TRUE if login was successful
*
* Convenience function to emit the #MatrixClient::login-finished signal.
*/
void
matrix_client_emit_login_finished(MatrixClient *matrix_client, gboolean success)
{
g_signal_emit(matrix_client, matrix_client_signals[SIGNAL_LOGIN_FINISHED], 0, success);
}
/**
* matrix_client_incoming_event:
* @client: an object implementing the #MatrixClient interface
* @room_id: the room the event is associated with
* @raw_event: the raw event
* @matrix_event: the event as a Matrix.Event
*
* Emits the #MatrixClient::event signal.
*/
void
matrix_client_incoming_event(MatrixClient *matrix_client, const gchar *room_id, JsonNode *raw_event, MatrixEventBase *matrix_event)
{
GQuark equark;
g_return_if_fail(raw_event != NULL);
if (matrix_event == NULL) {
equark = g_type_qname(MATRIX_EVENT_TYPE_BASE);
} else {
equark = g_type_qname(G_TYPE_FROM_INSTANCE((GObject*)matrix_event));
}
g_signal_emit(matrix_client, matrix_client_signals[SIGNAL_EVENT], equark, room_id, raw_event, matrix_event);
}
/**
* matrix_client_get_user_profile:
* @client: an object implementing the #MatrixClient interface
* @user_id: (not nullable): the user ID to get the profile for
* @room_id: (nullable): the room ID to get the profile from, or %NULL to get the global profile
* @error: (nullable): a #GError, or %NULL to ignore errors
*
* Get the profile of a user specified by @user_id. If @room_id is not %NULL, return the
* room-specific profile. If the users profile is not cached yet, @error is set to
* #MATRIX_ERROR_UNAVAILABLE.
*
* Returns: (nullable) (transfer none): a #MatrixProfile object
*/
MatrixProfile *
matrix_client_get_user_profile(MatrixClient *matrix_client, const gchar *user_id, const gchar *room_id, GError **error)
{
g_return_val_if_fail(matrix_client != NULL, NULL);
return MATRIX_CLIENT_GET_IFACE(matrix_client)->get_user_profile(matrix_client, user_id, room_id, error);
}
/**
* matrix_client_get_user_presence:
* @client: an object implementing the #MatrixClient interface
* @user_id: (not nullable): the user ID to get presence info for
* @room_id: (nullable): the room ID to get the presence info from, or %NULL to get the global
* presence info
* @error: (nullable): a #GError, or %NULL to ignore errors
*
* Get the presence state of a user specified by @user_id. If @room_id is %NULL, return the room
* specific presence state. If the user's presence state is not cached yet, @error is set to
* #MATRIX_ERROR_UNAVAILABLE.
*
* Returns: the presence of @user_id
*/
MatrixPresence
matrix_client_get_user_presence(MatrixClient *matrix_client, const gchar *user_id, const gchar *room_id, GError **error)
{
g_return_val_if_fail(matrix_client != NULL, 0);
return MATRIX_CLIENT_GET_IFACE(matrix_client)->get_user_presence(matrix_client, user_id, room_id, error);
}
/**
* matrix_client_get_room_by_id:
* @client: an object implementing the #MatrixClient interface
* @room_id: (not nullable): the room ID to get the room information for
* @error: (nullable): a #GError, or %NULL to ignore errors
*
* Get a room object by the room ID specified in @room_id. If room data is not cached yet, error
* is set to #MATRIX_ERROR_UNAVAILABLE and %NULL is returned.
*
* Returns: (nullable) (transfer none): a #MatrixRoom object corresponding to @room_id
*/
MatrixRoom *
matrix_client_get_room_by_id(MatrixClient *matrix_client, const gchar *room_id, GError **error)
{
g_return_val_if_fail(matrix_client != NULL, NULL);
return MATRIX_CLIENT_GET_IFACE(matrix_client)->get_room_by_id(matrix_client, room_id, error);
}
/**
* matrix_client_get_room_by_alias:
* @client: an object implementing the #MatrixClient interface
* @room_alias: (not nullable): a room alias
* @error: (nullable): a #GError, or %NULL to ignore errors
*
* Get a room object by the room alias specified in @room_alias. If room data is not cached yet,
* @error is set to #MATRIX_ERROR_UNAVAILABLE and %NULL is returned.
*
* Please note that this may be a lengthy operation, especially if there are many rooms with many
* aliases.
*
* Returns: (nullable) (transfer none): a #MatrixRoom object with @room_alias
*/
MatrixRoom *
matrix_client_get_room_by_alias(MatrixClient *matrix_client, const gchar *room_alias, GError **error)
{
g_return_val_if_fail(matrix_client != NULL, NULL);
return MATRIX_CLIENT_GET_IFACE(matrix_client)->get_room_by_alias(matrix_client, room_alias, error);
}
/**
* matrix_client_send:
* @client: a #MatrixClient
* @room_id: the room to send the event to
* @evt: the event to send
* @callback: (scope async): the callback function to call when the request is finished
* @user_data: user data to pass to the callback function
* @txn_id: the transaction ID to be used during this request. In case of a state event, it will be
* untouched
* @error: (nullable): a #GError, or %NULL to ignore errors
*
* Send an event to the given room. This should use the correct endpoint (e.g. the
* `/room/{roomId}/send` or the `/room/{roomId}/state` API in case of a HTTP connection) depending
* on the event: if the event has a state key (there is a state_key key in the generated JSON),
* even if an empty one, it will use the latter.
*/
void
matrix_client_send(MatrixClient *matrix_client,
const gchar *room_id,
MatrixEventBase *evt,
gulong txn_id,
MatrixClientSendCallback cb,
gpointer user_data,
GError **error)
{
g_return_if_fail(matrix_client != NULL);
MATRIX_CLIENT_GET_IFACE(matrix_client)->send(matrix_client, room_id, evt, cb, user_data, txn_id, error);
}
/**
* matrix_client_save_state:
* @client: an object implementing the #MatrixClient interface
* @filename: the filename to save state info to
* @error: a #GError, or %NULL to ignore errors
*
* Save the client state to a file. This may include server addresses and credentials, too, so a
* secure storage is highly recommended.
*/
void
matrix_client_save_state(MatrixClient *matrix_client, const gchar *filename, GError **error)
{
g_return_if_fail(matrix_client != NULL);
MATRIX_CLIENT_GET_IFACE(matrix_client)->save_state(matrix_client, filename, error);
}
/**
* matrix_client_load_state:
* @client: an object implementing the #MatrixClient interface
* @filename: the name of the file to load state from
* @error: (nullable): a #GError, or %NULL to ignore errors
*
* Load the state of the client, as saved by matrix_client_save_state().
*/
void
matrix_client_load_state(MatrixClient *matrix_client, const gchar *filename, GError **error)
{
g_return_if_fail(matrix_client != NULL);
MATRIX_CLIENT_GET_IFACE(matrix_client)->load_state(matrix_client, filename, error);
}
static void
matrix_client_real_login_finished(MatrixClient *matrix_client, gboolean success)
{}
static void
matrix_client_real_event(MatrixClient *matrix_client, const gchar *room_id, JsonNode *raw_event, MatrixEventBase *matrix_event)
{
g_return_if_fail(raw_event != NULL);
}
static void
matrix_client_default_init(MatrixClientInterface *iface)
{
static gboolean initialized = FALSE;
if (!initialized) {
initialized = TRUE;
iface->login_finished = matrix_client_real_login_finished;
iface->event = matrix_client_real_event;
/**
* MatrixClient::login-finished:
* @client: the #MatrixClient that emitted the signal
* @success: if %TRUE, login was successful
*
* This signal is a sign for a finished login request.
*
* Implementations are responsible for emitting this signal when they get a response for a
* login request.
*/
matrix_client_signals[SIGNAL_LOGIN_FINISHED] = g_signal_new(
"login-finished",
MATRIX_TYPE_CLIENT,
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(MatrixClientInterface, login_finished),
NULL, NULL,
g_cclosure_marshal_VOID__BOOLEAN,
G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
/**
* MatrixClient::event:
* @client: the #MatrixClient that emitted the signal
* @room_id: the ID of the room associated with this event
* @raw_event: the raw event as a JSON object
* @matrix_event: the event as a #MatrixEventBase derived object
*
* This signal is a sign of an incoming event.
*
* It gets emitted for every event, regardless if it is handled by other event signals,
* before other signals.
*
* Implementations are responsible for emitting this signal when any kind of event arrives
* from the event stream or the history.
*/
matrix_client_signals[SIGNAL_EVENT] = g_signal_new(
"event",
MATRIX_TYPE_CLIENT,
G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
G_STRUCT_OFFSET(MatrixClientInterface, event),
NULL, NULL,
_matrix_marshal_VOID__STRING_BOXED_OBJECT,
G_TYPE_NONE, 3, G_TYPE_STRING, JSON_TYPE_NODE, MATRIX_EVENT_TYPE_BASE);
/**
* MatrixClient::polling-started:
* @client: the #MatrixClient that emitted the signal
*
* This signal is emitted when polling is started.
*/
matrix_client_signals[SIGNAL_POLLING_STARTED] = g_signal_new(
"polling-started",
MATRIX_TYPE_CLIENT,
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
/**
* MatrixClient::polling-stopped:
* @client: the object that sent the signal
* @error: gets set to an actual error if polling is stopped due to one
*
* This signal gets invoked when polling is stopped due to any reason.
*/
matrix_client_signals[SIGNAL_POLLING_STOPPED] = g_signal_new(
"polling-stopped",
MATRIX_TYPE_CLIENT,
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__BOXED,
G_TYPE_NONE, 1, G_TYPE_ERROR);
}
}
/**
* matrix_client_connect_event:
* @client: a #MatrixClient
* @event_gtype: the #GType of a #MatrixEventBase derived type
* @callback: (scope async): the callback function to connect
* @user_data: user data passed to the callback function
* @destroy_notify: function to call on @user_data when it can be destroyed
*
* Connect a handler for events. If @event_gtype is #MATRIX_EVENT_TYPE_BASE, all events will be
* sent to the callback function, otherwise only events that match the specified event type.
*
* If @event_gtype is not derived from #MatrixEventBase, @callback wont get connected.
*/
void
matrix_client_connect_event(MatrixClient *client,
GType event_gtype,
MatrixClientEventCallback callback,
gpointer user_data,
GDestroyNotify destroy_notify)
{
GClosure *closure;
GQuark equark;
MatrixEventBaseClass *event_class = MATRIX_EVENT_BASE_CLASS(
g_type_class_ref(event_gtype));
guint event_signal_id = g_signal_lookup("event", MATRIX_TYPE_CLIENT);
if (!MATRIX_EVENT_IS_BASE_CLASS(event_class)) {
g_warning("Trying to connect to a type that is not derived from MatrixEvent");
g_type_class_unref(event_class);
return;
}
g_type_class_unref(event_class);
equark = g_type_qname(event_gtype);
closure = g_closure_ref(g_cclosure_new(G_CALLBACK(callback),
user_data,
(GClosureNotify)destroy_notify));
g_closure_set_marshal(closure,
_matrix_marshal_VOID__STRING_BOXED_OBJECT);
g_closure_sink(closure);
g_signal_connect_closure_by_id(client,
event_signal_id, equark,
closure, FALSE);
}