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

879 lines
32 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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-http-client.h"
#include "matrix-client.h"
#include "matrix-event-room-base.h"
#include "matrix-event-presence.h"
#include "matrix-event-room-member.h"
#include "matrix-event-room-aliases.h"
#include "matrix-event-room-avatar.h"
#include "matrix-event-room-canonical-alias.h"
#include "matrix-event-room-create.h"
#include "matrix-event-room-guest-access.h"
#include "matrix-event-room-history-visibility.h"
#include "matrix-event-room-join-rules.h"
#include "matrix-event-room-name.h"
#include "matrix-event-room-power-levels.h"
#include "matrix-event-room-topic.h"
/**
* SECTION:matrix-http-client
* @short_description: event-driven communication with Matrix.org homeserver via HTTP.
*
* An event-driven client class to communicate with HTTP based Matrix.org servers.
*/
static void matrix_http_client_matrix_client_interface_init (MatrixClientInterface * iface);
typedef struct {
gboolean _polling;
gulong _event_timeout;
gchar* _last_sync_token;
GHashTable* _user_global_profiles;
GHashTable* _user_global_presence;
GHashTable* _rooms;
gulong _last_txn_id;
} MatrixHTTPClientPrivate;
G_DEFINE_TYPE_EXTENDED(MatrixHTTPClient, matrix_http_client, MATRIX_TYPE_HTTP_API, 0, G_ADD_PRIVATE(MatrixHTTPClient) G_IMPLEMENT_INTERFACE(MATRIX_TYPE_CLIENT, matrix_http_client_matrix_client_interface_init));
MatrixHTTPClient *
matrix_http_client_new(const gchar* base_url)
{
g_return_val_if_fail(base_url != NULL, NULL);
return (MatrixHTTPClient *)g_object_new(MATRIX_TYPE_HTTP_CLIENT,
"base-url", base_url,
NULL);
}
static void
login_callback(MatrixAPI *matrix_api, const gchar *content_type, JsonNode *json_content, GByteArray *raw_content, GError *err, gpointer user_data)
{
g_signal_emit_by_name((MatrixClient *)matrix_api,
"login-finished",
g_error_matches(err, MATRIX_ERROR, MATRIX_ERROR_NONE));
}
static void
matrix_http_client_real_login_with_password(MatrixClient *matrix_client, const gchar *username, const gchar *password, GError **error)
{
JsonBuilder *builder;
JsonNode *root_node;
g_return_if_fail (username != NULL);
g_return_if_fail (password != NULL);
builder = json_builder_new();
json_builder_begin_object(builder);
json_builder_set_member_name(builder, "user");
json_builder_add_string_value(builder, username);
json_builder_set_member_name(builder, "password");
json_builder_add_string_value(builder, password);
json_builder_end_object(builder);
root_node = json_builder_get_root(builder);
matrix_api_login(MATRIX_API(matrix_client), "m.login.password", root_node, login_callback, NULL, error);
json_node_unref(root_node);
g_object_unref(builder);
}
static void
matrix_http_client_real_register_with_password(MatrixClient *matrix_client, const gchar *username, const gchar *password, GError **error)
{
matrix_api_register_account(MATRIX_API(matrix_client),
MATRIX_ACCOUNT_KIND_USER, FALSE, username, password,
login_callback, NULL,
error);
}
static void
logout_callback(MatrixAPI *matrix_api, const gchar *content_type, JsonNode *json_content, GByteArray *raw_content, GError *err, gpointer user_data)
{
matrix_api_abort_pending(matrix_api);
matrix_api_set_token(matrix_api, NULL);
}
static void
matrix_http_client_real_logout(MatrixClient *matrix_client, GError **error)
{
matrix_api_logout(MATRIX_API(matrix_client), logout_callback, NULL, error);
}
static MatrixRoom *
_get_or_create_room(MatrixHTTPClient *matrix_http_client, const gchar *room_id)
{
MatrixRoom *room = NULL;
MatrixHTTPClientPrivate *priv;
g_return_val_if_fail(matrix_http_client != NULL, NULL);
g_return_val_if_fail(room_id != NULL, NULL);
priv = matrix_http_client_get_instance_private(matrix_http_client);
if ((room = g_hash_table_lookup(priv->_rooms, room_id)) == NULL) {
room = matrix_room_new(room_id);
g_hash_table_insert(priv->_rooms, g_strdup(room_id), room);
}
return room;
}
static void
_process_event(MatrixHTTPClient *matrix_http_client, JsonNode *event_node, const gchar *room_id)
{
MatrixHTTPClientPrivate *priv;
JsonObject *root;
JsonNode *node;
const gchar *event_type;
MatrixEventBase *evt = NULL;
GError *inner_error = NULL;
g_return_if_fail(matrix_http_client != NULL);
g_return_if_fail(event_node != NULL);
if (json_node_get_node_type(event_node) != JSON_NODE_OBJECT) {
#if DEBUG
g_warning("Received event that is not an object.");
#endif
return;
}
root = json_node_get_object(event_node);
if ((node = json_object_get_member(root, "type")) == NULL) {
#if DEBUG
g_warning("Received event without type.");
#endif
return;
}
priv = matrix_http_client_get_instance_private(matrix_http_client);
event_type = json_node_get_string(node);
evt = matrix_event_base_new_from_json(event_type, event_node, &inner_error);
if (inner_error != NULL) {
evt = NULL;
g_clear_error(&inner_error);
}
if (evt != NULL) {
if (MATRIX_EVENT_IS_ROOM(evt)) {
MatrixEventRoom *revt = MATRIX_EVENT_ROOM(evt);
// Make sure Room events have room_id set, even if it was stripped by the HS
if (matrix_event_room_get_room_id(revt) == NULL) {
matrix_event_room_set_room_id(revt, room_id);
}
}
else if (MATRIX_EVENT_IS_PRESENCE(evt)) {
MatrixEventPresence *pevt = MATRIX_EVENT_PRESENCE(evt);
const gchar *user_id = matrix_event_presence_get_user_id(pevt);
MatrixProfile *profile;
g_hash_table_replace(priv->_user_global_presence, g_strdup(user_id), GINT_TO_POINTER(matrix_event_presence_get_presence(pevt)));
profile = g_hash_table_lookup(priv->_user_global_profiles, user_id);
if (profile == NULL) {
profile = matrix_profile_new();
g_hash_table_insert(priv->_user_global_profiles, g_strdup(user_id), profile);
}
matrix_profile_set_avatar_url(profile, matrix_event_presence_get_avatar_url(pevt));
matrix_profile_set_display_name(profile, matrix_event_presence_get_display_name(pevt));
} else if (MATRIX_EVENT_IS_ROOM(evt)) {
MatrixEventRoom *revt = MATRIX_EVENT_ROOM(evt);
MatrixRoom *room = _get_or_create_room(matrix_http_client, matrix_event_room_get_room_id(revt));
if (MATRIX_EVENT_IS_ROOM_MEMBER(evt)) {
MatrixEventRoomMember *mevt = MATRIX_EVENT_ROOM_MEMBER(evt);
const gchar *user_id = matrix_event_room_member_get_user_id(mevt);
MatrixProfile *profile = matrix_room_get_or_add_member(room, user_id, matrix_event_room_member_get_tpi_display_name(mevt) != NULL, NULL);
matrix_profile_set_avatar_url(profile, matrix_event_room_member_get_avatar_url(mevt));
matrix_profile_set_display_name(profile, matrix_event_room_member_get_display_name(mevt));
} else if (MATRIX_EVENT_IS_ROOM_ALIASES(evt)) {
gint n_aliases;
const gchar **aliases;
MatrixEventRoomAliases *aevt = MATRIX_EVENT_ROOM_ALIASES(evt);
aliases = matrix_event_room_aliases_get_aliases(aevt, &n_aliases);
matrix_room_set_aliases(room, aliases, n_aliases);
} else if (MATRIX_EVENT_IS_ROOM_AVATAR(evt)) {
MatrixEventRoomAvatar *aevt = MATRIX_EVENT_ROOM_AVATAR(evt);
matrix_room_set_avatar_url(room, matrix_event_room_avatar_get_url(aevt));
matrix_room_set_avatar_info(room, matrix_event_room_avatar_get_info(aevt));
matrix_room_set_avatar_thumbnail_url(room, matrix_event_room_avatar_get_thumbnail_url(aevt));
matrix_room_set_avatar_thumbnail_info(room, matrix_event_room_avatar_get_thumbnail_info(aevt));
} else if (MATRIX_EVENT_IS_ROOM_CANONICAL_ALIAS(evt)) {
MatrixEventRoomCanonicalAlias *cevt = MATRIX_EVENT_ROOM_CANONICAL_ALIAS(evt);
matrix_room_set_canonical_alias(room, matrix_event_room_canonical_alias_get_canonical_alias(cevt));
} else if (MATRIX_EVENT_IS_ROOM_CREATE(evt)) {
MatrixEventRoomCreate *cevt = MATRIX_EVENT_ROOM_CREATE(evt);
matrix_room_set_creator(room, matrix_event_room_create_get_creator(cevt));
matrix_room_set_federate(room, matrix_event_room_create_get_federate(cevt));
} else if (MATRIX_EVENT_IS_ROOM_GUEST_ACCESS(evt)) {
MatrixEventRoomGuestAccess *gevt = MATRIX_EVENT_ROOM_GUEST_ACCESS(evt);
matrix_room_set_guest_access(room, matrix_event_room_guest_access_get_guest_access(gevt));
} else if (MATRIX_EVENT_IS_ROOM_HISTORY_VISIBILITY(evt)) {
MatrixEventRoomHistoryVisibility *hevt = MATRIX_EVENT_ROOM_HISTORY_VISIBILITY(evt);
matrix_room_set_history_visibility(room, matrix_event_room_history_visibility_get_visibility(hevt));
} else if (MATRIX_EVENT_IS_ROOM_JOIN_RULES(evt)) {
MatrixEventRoomJoinRules *jevt = MATRIX_EVENT_ROOM_JOIN_RULES(evt);
matrix_room_set_join_rules(room, matrix_event_room_join_rules_get_join_rules(jevt));
} else if (MATRIX_EVENT_ROOM_NAME(evt)) {
MatrixEventRoomName *nevt = MATRIX_EVENT_ROOM_NAME(evt);
matrix_room_set_name(room, matrix_event_room_name_get_name(nevt));
} else if (MATRIX_EVENT_ROOM_POWER_LEVELS(evt)) {
MatrixEventRoomPowerLevels *levt = MATRIX_EVENT_ROOM_POWER_LEVELS(evt);
GHashTable *user_levels;
GHashTable *event_levels;
GHashTableIter iter;
gchar *key;
gpointer level;
matrix_room_set_default_power_level(room, matrix_event_room_power_levels_get_users_default(levt));
matrix_room_set_default_event_level(room, matrix_event_room_power_levels_get_events_default(levt));
matrix_room_set_default_state_level(room, matrix_event_room_power_levels_get_state_default(levt));
matrix_room_set_ban_level(room, matrix_event_room_power_levels_get_ban(levt));
matrix_room_set_kick_level(room, matrix_event_room_power_levels_get_kick(levt));
matrix_room_set_redact_level(room, matrix_event_room_power_levels_get_redact(levt));
matrix_room_set_invite_level(room, matrix_event_room_power_levels_get_invite(levt));
matrix_room_clear_user_levels(room);
matrix_room_clear_event_levels(room);
user_levels = matrix_event_room_power_levels_get_user_levels(levt);
g_hash_table_iter_init(&iter, user_levels);
while (g_hash_table_iter_next(&iter, (gpointer *)&key, &level)) {
matrix_room_set_user_level(room, key, GPOINTER_TO_INT(level));
}
event_levels = matrix_event_room_power_levels_get_event_levels(levt);
g_hash_table_iter_init(&iter, event_levels);
while (g_hash_table_iter_next(&iter, (gpointer *)&key, &level)) {
matrix_room_set_event_level(room, key, GPOINTER_TO_INT(level));
}
} else if (MATRIX_EVENT_IS_ROOM_TOPIC(evt)) {
MatrixEventRoomTopic *tevt = MATRIX_EVENT_ROOM_TOPIC(evt);
matrix_room_set_topic(room, matrix_event_room_topic_get_topic(tevt));
}
}
}
matrix_client_incoming_event(MATRIX_CLIENT(matrix_http_client), room_id, event_node, evt);
g_object_unref(evt);
json_node_unref(node);
json_object_unref(root);
}
static void
_process_event_list_obj(MatrixHTTPClient *matrix_http_client, JsonNode* node, const gchar* room_id)
{
JsonObject *root;
JsonNode *events_node;
root = json_node_get_object(node);
if ((events_node = json_object_get_member(root, "events")) != NULL) {
if (json_node_get_node_type(events_node) == JSON_NODE_ARRAY) {
JsonArray *events_array = json_node_get_array(events_node);
gint len = json_array_get_length(events_array);
for (gint idx = 0; idx < len; idx++) {
JsonNode *event_node = json_array_get_element(events_array, idx);
_process_event(matrix_http_client, event_node, room_id);
}
}
}
}
static void
cb_sync(MatrixAPI *matrix_api, const gchar *content_type, JsonNode *json_content, GByteArray *raw_content, GError *error, gpointer user_data)
{
MatrixHTTPClientPrivate *priv = matrix_http_client_get_instance_private(MATRIX_HTTP_CLIENT(matrix_api));
if (error == NULL) {
JsonObject *root = json_node_get_object(json_content);
JsonNode *node;
#if DEBUG
g_debug("Processing account data");
#endif
_process_event_list_obj(MATRIX_HTTP_CLIENT(matrix_api), json_object_get_member(root, "account_data"), NULL);
#if DEBUG
g_debug("Processing presence");
#endif
_process_event_list_obj(MATRIX_HTTP_CLIENT(matrix_api), json_object_get_member(root, "presence"), NULL);
if ((node = json_object_get_member(root, "rooms")) != NULL) {
if (json_node_get_node_type(node) == JSON_NODE_OBJECT) {
JsonObject *rooms_root = json_node_get_object(node);
JsonNode *rooms_node;
MatrixHTTPClient *matrix_http_client = MATRIX_HTTP_CLIENT(matrix_api);
#if DEBUG
g_debug("Processing rooms");
#endif
if ((rooms_node = json_object_get_member(rooms_root, "invite")) != NULL) {
JsonObjectIter iter;
const gchar *room_id;
JsonNode *room_node;
json_object_iter_init(&iter, json_node_get_object(rooms_node));
while (json_object_iter_next(&iter, &room_id, &room_node)) {
JsonObject *room_root;
if (json_node_get_node_type(room_node) != JSON_NODE_OBJECT) {
continue;
}
room_root = json_node_get_object(room_node);
_process_event_list_obj(MATRIX_HTTP_CLIENT(matrix_api), json_object_get_member(room_root, "invite_state"), room_id);
}
}
if ((rooms_node = json_object_get_member(rooms_root, "join")) != NULL) {
JsonObjectIter iter;
const gchar *room_id;
JsonNode *room_node;
json_object_iter_init(&iter, json_node_get_object(rooms_node));
while (json_object_iter_next(&iter, &room_id, &room_node)) {
JsonObject *room_root;
if (json_node_get_node_type(room_node) != JSON_NODE_OBJECT) {
continue;
}
room_root = json_node_get_object(room_node);
_process_event_list_obj(matrix_http_client, json_object_get_member(room_root, "timeline"), room_id);
_process_event_list_obj(matrix_http_client, json_object_get_member(room_root, "state"), room_id);
_process_event_list_obj(matrix_http_client, json_object_get_member(room_root, "account_data"), room_id);
_process_event_list_obj(matrix_http_client, json_object_get_member(room_root, "ephemeral"), room_id);
}
}
if ((rooms_node = json_object_get_member(rooms_root, "leave")) != NULL) {
JsonObjectIter iter;
const gchar *room_id;
JsonNode *room_node;
json_object_iter_init(&iter, json_node_get_object(rooms_node));
while (json_object_iter_next(&iter, &room_id, &room_node)) {
JsonObject *room_root;
if (json_node_get_node_type(room_node) != JSON_NODE_OBJECT) {
continue;
}
room_root = json_node_get_object(room_node);
_process_event_list_obj(matrix_http_client, json_object_get_member(room_root, "timeline"), room_id);
_process_event_list_obj(matrix_http_client, json_object_get_member(room_root, "state"), room_id);
}
}
}
}
if ((node = json_object_get_member(root, "next_batch")) != NULL) {
g_free(priv->_last_sync_token);
priv->_last_sync_token = g_strdup(json_node_get_string(node));
}
} else if ((error->domain == MATRIX_ERROR) &&
((error->code == MATRIX_ERROR_M_FORBIDDEN) ||
(error->code == MATRIX_ERROR_M_UNKNOWN_TOKEN) ||
(error->code == MATRIX_ERROR_M_UNAUTHORIZED))) {
matrix_api_set_token(matrix_api, NULL);
}
// It is possible that polling has been disabled while we were processing events. Dont
// continue polling if that is the case.
if (priv->_polling) {
if ((error == NULL) || (error->code < MATRIX_ERROR_M_MISSING_TOKEN)) {
matrix_client_begin_polling(MATRIX_CLIENT(matrix_api), NULL);
} else if ((error != NULL) && (error->code >= MATRIX_ERROR_M_MISSING_TOKEN)) {
g_signal_emit_by_name(MATRIX_CLIENT(matrix_api), "polling-stopped", error);
matrix_client_stop_polling(MATRIX_CLIENT(matrix_api), FALSE, NULL);
}
}
}
static void
matrix_http_client_real_begin_polling(MatrixClient *matrix_client, GError **error)
{
MatrixHTTPClientPrivate *priv = matrix_http_client_get_instance_private(MATRIX_HTTP_CLIENT(matrix_client));
GError *inner_error = NULL;
matrix_api_sync(MATRIX_API(matrix_client),
NULL, NULL,
priv->_last_sync_token, FALSE, FALSE,
priv->_event_timeout,
cb_sync, NULL,
&inner_error);
if (inner_error != NULL) {
g_propagate_error(error, inner_error);
return;
}
if (priv->_polling == FALSE) {
g_signal_emit_by_name (matrix_client, "polling-started");
}
priv->_polling = TRUE;
}
static void
matrix_http_client_real_stop_polling (MatrixClient *matrix_client, gboolean cancel_ongoing, GError **error)
{
MatrixHTTPClientPrivate *priv = matrix_http_client_get_instance_private(MATRIX_HTTP_CLIENT(matrix_client));
priv->_polling = FALSE;
if (cancel_ongoing) {
matrix_api_abort_pending(MATRIX_API(matrix_client));
}
}
static MatrixProfile *
matrix_http_client_real_get_user_profile(MatrixClient *matrix_client, const gchar *user_id, const gchar *room_id, GError **error)
{
MatrixHTTPClientPrivate *priv = matrix_http_client_get_instance_private(MATRIX_HTTP_CLIENT(matrix_client));
MatrixRoom *room;
if (room_id == NULL) {
MatrixProfile *profile = g_hash_table_lookup(priv->_user_global_profiles, user_id);
if (profile == NULL) {
g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_UNAVAILABLE,
"Global profile for %s is not cached yet.",
user_id);
}
return profile;
}
if ((room = g_hash_table_lookup(priv->_rooms, room_id)) == NULL) {
g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_UNAVAILABLE,
"Room data for %s is not cached yet.", room_id);
return NULL;
}
return matrix_room_get_member(room, user_id, NULL, error);
}
static MatrixPresence
matrix_http_client_real_get_user_presence(MatrixClient *matrix_client, const gchar *user_id, const gchar *room_id, GError **error)
{
MatrixHTTPClientPrivate *priv = matrix_http_client_get_instance_private(MATRIX_HTTP_CLIENT(matrix_client));
gpointer value;
if (room_id != NULL) {
g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_UNSUPPORTED,
"Per-room presences are not supported yet.");
return MATRIX_PRESENCE_UNKNOWN;
}
if (!g_hash_table_lookup_extended(priv->_user_global_presence, user_id, NULL, &value)) {
g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_UNAVAILABLE,
"Global presence for %s is not cached yet.",
user_id);
return MATRIX_PRESENCE_UNKNOWN;
}
return GPOINTER_TO_INT(value);
}
static MatrixRoom *
matrix_http_client_real_get_room_by_id(MatrixClient *matrix_client, const gchar *room_id, GError **error)
{
MatrixHTTPClientPrivate *priv;
MatrixRoom *room;
g_return_val_if_fail(room_id != NULL, NULL);
priv = matrix_http_client_get_instance_private(MATRIX_HTTP_CLIENT(matrix_client));
if ((room = g_hash_table_lookup(priv->_rooms, room_id)) == NULL) {
g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_UNAVAILABLE,
"Room data for %s is not cached yet.", room_id);
return NULL;
}
return room;
}
static MatrixRoom *
matrix_http_client_real_get_room_by_alias(MatrixClient *matrix_client, const gchar *room_alias, GError **error)
{
MatrixHTTPClientPrivate *priv;
MatrixRoom *room_found = NULL;
GHashTableIter iter;
gpointer key;
gpointer value;
g_return_val_if_fail (room_alias != NULL, NULL);
priv = matrix_http_client_get_instance_private(MATRIX_HTTP_CLIENT(matrix_client));
g_hash_table_iter_init(&iter, priv->_rooms);
while (g_hash_table_iter_next(&iter, &key, &value)) {
MatrixRoom *room = value;
gchar **aliases;
gint n_aliases;
if (g_strcmp0(room_alias, matrix_room_get_canonical_alias(room)) == 0) {
room_found = room;
break;
}
aliases = matrix_room_get_aliases(room, &n_aliases);
for (gint i = 0; i < n_aliases; i++) {
if (g_strcmp0(room_alias, aliases[i]) == 0) {
room_found = room;
// Whee!
goto found_room;
}
}
}
found_room:
if (room_found == NULL) {
g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_UNAVAILABLE,
"No room data found for alias %s", room_alias);
}
return room_found;
}
/**
* matrix_http_client_next_txn_id:
* @client: a #MatrixHTTPClient
*
* Get the next transaction ID to use. It increments the internally stored value and returns
* that, so it is guaranteed to be unique until we run out of #ulong boundaries.
*
* It is called internally by send().
*/
gulong
matrix_http_client_next_txn_id(MatrixHTTPClient *matrix_http_client)
{
MatrixHTTPClientPrivate *priv;
g_return_val_if_fail(matrix_http_client != NULL, 0UL);
priv = matrix_http_client_get_instance_private(matrix_http_client);
return ++(priv->_last_txn_id);
}
typedef struct {
MatrixClientSendCallback cb;
gpointer callback_target;
} SendCallbackData;
static void
send_callback(MatrixAPI *matrix_api, const gchar *content_type, JsonNode *json_content, GByteArray *raw_content, GError *err, gpointer user_data)
{
const gchar *event_id = NULL;
GError *new_err = err;
SendCallbackData *cb_data = user_data;
g_return_if_fail(matrix_api != NULL);
// If there is no callback, there is no point to continue
if (cb_data->cb == NULL) {
return;
}
if (err == NULL) {
JsonObject *root = json_node_get_object(json_content);
if (json_object_has_member(root, "event_id")) {
event_id = json_object_get_string_member(root, "event_id");
} else {
new_err = g_error_new_literal(MATRIX_ERROR, MATRIX_ERROR_BAD_RESPONSE,
"event_id is missing from an event response");
}
}
cb_data->cb(MATRIX_CLIENT(matrix_api), event_id, new_err, cb_data->callback_target);
}
static void
matrix_http_client_real_send(MatrixClient *matrix_client, const gchar *room_id, MatrixEventBase *evt, MatrixClientSendCallback cb, void *cb_target, gulong txn_id, GError **error)
{
JsonNode *evt_node;
JsonObject *evt_root;
const gchar *state_key = NULL;
SendCallbackData cb_data;
g_return_if_fail (room_id != NULL);
g_return_if_fail (evt != NULL);
evt_node = matrix_event_base_get_json(evt);
evt_root = json_node_get_object(evt_node);
if (json_object_has_member(evt_root, "state_key")) {
state_key = json_object_get_string_member(evt_root, "state_key");
}
cb_data.cb = cb;
cb_data.callback_target = cb_target;
if (state_key != NULL) {
matrix_api_send_state_event(MATRIX_API(matrix_client),
room_id,
matrix_event_base_get_event_type(evt),
state_key,
json_object_get_member(evt_root, "content"),
send_callback, &cb_data,
error);
} else {
txn_id = matrix_http_client_next_txn_id(MATRIX_HTTP_CLIENT(matrix_client));
gchar *txn_id_str = g_strdup_printf("%lu", txn_id);
matrix_api_send_event(MATRIX_API(matrix_client),
room_id,
matrix_event_base_get_event_type(evt),
txn_id_str,
json_object_get_member(evt_root, "content"),
send_callback, &cb_data,
error);
g_free(txn_id_str);
}
}
static void
matrix_http_client_real_save_state(MatrixClient *matrix_client, const gchar *filename, GError * *error)
{
JsonObject *root;
const gchar *user_id;
const gchar *homeserver;
const gchar *token;
JsonNode *node;
JsonGenerator *generator;
g_return_if_fail(filename != NULL);
root = json_object_new();
json_object_set_string_member(root, "base_url", matrix_http_api_get_base_url(MATRIX_HTTP_API(matrix_client)));
json_object_set_boolean_member(root, "validate_certificate", matrix_http_api_get_validate_certificate(MATRIX_HTTP_API(matrix_client)));
if ((user_id = matrix_api_get_user_id(MATRIX_API(matrix_client))) != NULL) {
json_object_set_string_member(root, "user_id", user_id);
}
if ((homeserver = matrix_api_get_homeserver(MATRIX_API(matrix_client))) != NULL) {
json_object_set_string_member(root, "homeserver_name", homeserver);
}
if ((token = matrix_api_get_token(MATRIX_API(matrix_client))) != NULL) {
json_object_set_string_member(root, "access_token", token);
}
node = json_node_new(JSON_NODE_OBJECT);
json_node_set_object(node, root);
#if DEBUG
g_debug("Saving state to %s\n", filename);
#endif
generator = json_generator_new();
json_generator_set_root(generator, node);
json_generator_to_file(generator, filename, error);
}
static void
matrix_http_client_real_load_state(MatrixClient *matrix_client, const gchar *filename, GError **error)
{
JsonParser *parser;
JsonNode *node;
JsonNode *root_node;
JsonObject *root;
g_return_if_fail(filename != NULL);
parser = json_parser_new();
#if DEBUG
g_debug("Loading state from %s\n", filename);
#endif
if (!json_parser_load_from_file(parser, filename, error)) {
g_object_unref(parser);
return;
}
root_node = json_node_ref(json_parser_get_root(parser));
g_object_unref(parser);
if (json_node_get_node_type(root_node) != JSON_NODE_OBJECT) {
g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_INVALID_FORMAT,
"Save data must be a JSON object.");
json_node_unref(root_node);
return;
}
root = json_node_get_object(root_node);
if ((node = json_object_get_member(root, "base_url")) == NULL) {
g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_INVALID_FORMAT,
"Save data has no base_url key");
json_node_unref(root_node);
return;
}
matrix_http_api_set_base_url(MATRIX_HTTP_API(matrix_client), json_node_get_string(node));
#if DEBUG
g_debug("Loaded base URL %s", json_node_get_string(node));
#endif
if ((node = json_object_get_member(root, "validate_certificate")) == NULL) {
g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_INVALID_FORMAT,
"Save data has no validate_certificate key");
json_node_unref(root_node);
return;
}
matrix_http_api_set_validate_certificate(MATRIX_HTTP_API(matrix_client), json_node_get_boolean(node));
if ((node = json_object_get_member(root, "user_id")) != NULL) {
matrix_api_set_user_id(MATRIX_API(matrix_client), json_node_get_string(node));
#if DEBUG
g_debug("Loaded user ID %s", matrix_api_get_user_id(MATRIX_API(matrix_client)));
#endif
}
if ((node = json_object_get_member(root, "homeserver_name")) != NULL) {
matrix_api_set_homeserver(MATRIX_API(matrix_client), json_node_get_string(node));
#if DEBUG
g_debug("Loaded homeserver name %s", matrix_api_get_homeserver(MATRIX_API(matrix_client)));
#endif
}
if ((node = json_object_get_member(root, "access_token")) != NULL) {
matrix_api_set_token(MATRIX_API(matrix_client), json_node_get_string(node));
#if DEBUG
g_debug("Loaded access token %s", matrix_api_get_token(MATRIX_API(matrix_client)));
#endif
}
json_node_unref(root_node);
}
static void
matrix_http_client_finalize(GObject *gobject)
{
MatrixHTTPClientPrivate *priv = matrix_http_client_get_instance_private(MATRIX_HTTP_CLIENT(gobject));
g_free(priv->_last_sync_token);
g_hash_table_unref(priv->_user_global_profiles);
g_hash_table_unref(priv->_user_global_presence);
g_hash_table_unref(priv->_rooms);
G_OBJECT_CLASS(matrix_http_client_parent_class)->finalize(gobject);
}
static void
matrix_http_client_class_init(MatrixHTTPClientClass *klass)
{
G_OBJECT_CLASS(klass)->finalize = matrix_http_client_finalize;
}
static void
matrix_http_client_matrix_client_interface_init(MatrixClientInterface * iface)
{
iface->login_with_password = matrix_http_client_real_login_with_password;
iface->register_with_password = matrix_http_client_real_register_with_password;
iface->logout = matrix_http_client_real_logout;
iface->begin_polling = matrix_http_client_real_begin_polling;
iface->stop_polling = matrix_http_client_real_stop_polling;
iface->get_user_profile = matrix_http_client_real_get_user_profile;
iface->get_user_presence = matrix_http_client_real_get_user_presence;
iface->get_room_by_id = matrix_http_client_real_get_room_by_id;
iface->get_room_by_alias = matrix_http_client_real_get_room_by_alias;
iface->send = matrix_http_client_real_send;
iface->save_state = matrix_http_client_real_save_state;
iface->load_state = matrix_http_client_real_load_state;
}
static void
matrix_http_client_init(MatrixHTTPClient *matrix_http_client)
{
MatrixHTTPClientPrivate *priv = matrix_http_client_get_instance_private(matrix_http_client);
priv->_polling = FALSE;
priv->_event_timeout = (gulong)30000;
priv->_user_global_profiles = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_object_unref);
priv->_user_global_presence = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
priv->_rooms = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_object_unref);
priv->_last_txn_id = (gulong)0;
}