diff --git a/.gitignore b/.gitignore index 5b60f95..dbbf283 100644 --- a/.gitignore +++ b/.gitignore @@ -51,7 +51,6 @@ Makefile.in /src/matrix-client.c /src/matrix-http-api.c /src/matrix-http-client.c -/src/matrix-event-base.c /src/matrix-event-presence.c /src/matrix-event-room-base.c /src/matrix-event-room-member.c diff --git a/src/Makefile.am b/src/Makefile.am index 53e35b6..3b6afd0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -21,7 +21,6 @@ libmatrix_glib_0_0_la_VALA_SOURCES = \ matrix-client.vala \ matrix-http-api.vala \ matrix-http-client.vala \ - matrix-event-base.vala \ matrix-event-room-base.vala \ matrix-event-state-base.vala \ matrix-event-presence.vala \ @@ -113,6 +112,7 @@ bin_PROGRAMS = test-api-client test-client INST_H_SRC_FILES = \ matrix-types.h \ matrix-compacts.h \ + matrix-event-base.h \ utils.h \ matrix-profile.h \ $(NULL) @@ -135,6 +135,7 @@ libmatrix_glib_0_0_la_SOURCES = \ matrix-version.c \ matrix-types.c \ matrix-compacts.c \ + matrix-event-base.c \ matrix-profile.c \ utils.c \ matrix-enumtypes.c \ diff --git a/src/matrix-event-base.c b/src/matrix-event-base.c new file mode 100644 index 0000000..9b9d70d --- /dev/null +++ b/src/matrix-event-base.c @@ -0,0 +1,603 @@ +/* + * 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 + * . + */ + +#include "matrix-event-base.h" +#include "matrix-types.h" +#include "matrix-enumtypes.h" +#include "config.h" + +/** + * SECTION:matrix-event-base + * @short_description: abstract base class for Matrix events + * @title: Base class for events + * + * #MatrixEventBase is the abstract base class of Matrix events. All event classes in this + * library are derived from it, and custom classes should derived from it, too, if one wants to + * benefit from #MatrixClient’s (de)serialization capabilities. + * + * This class only defines the MatrixEventBase:event-type property, which is tied to the + * `event_type` field in a JSON representation. Subclasses should not get or set this property. + * + * Event objects can be created from JSON data (a #JsonNode) by calling + * matrix_event_base_new_from_json(), which will return the correct GObject type as long as + * it was registered with matrix_event_register_type(). + */ +enum { + PROP_0, + PROP_EVENT_TYPE, + PROP_JSON, + NUM_PROPS +}; + +static GParamSpec* matrix_event_base_properties[NUM_PROPS]; +static GHashTable *matrix_event_type_handlers = NULL; + +typedef struct { + GError* _construct_error; + gboolean _inited; + JsonNode* _json; + gchar *_event_type; +} MatrixEventBasePrivate; + +static void matrix_event_base_g_initable_interface_init (GInitableIface *iface); + +/** + * MatrixEventBase: + * + * Abstract base class for event handlers. + */ + +/** + * MatrixEventBaseClass: + * @from_json: function to initialize themselves from JSON data + * @to_json: function to export their data to JSON + * + * Class structure for #MatrixEventBase. + */ +G_DEFINE_TYPE_EXTENDED(MatrixEventBase, matrix_event_base, G_TYPE_OBJECT, G_TYPE_FLAG_ABSTRACT, G_ADD_PRIVATE (MatrixEventBase) G_IMPLEMENT_INTERFACE(G_TYPE_INITABLE, matrix_event_base_g_initable_interface_init)); + +static gboolean +matrix_event_base_initable_init(GInitable *g_initable, GCancellable *cancellable, GError **error) +{ + MatrixEventBasePrivate *priv; + + if (cancellable != NULL) { + g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_UNSUPPORTED, + "Cancellable initialization not supported"); + + return FALSE; + } + + priv = matrix_event_base_get_instance_private(MATRIX_EVENT_BASE(g_initable)); + + if (priv->_construct_error != NULL) { + g_propagate_error(error, priv->_construct_error); + + return FALSE; + } + + priv->_inited = TRUE; + + return TRUE; +} + +static void +matrix_event_base_initialize_from_json(MatrixEventBase *matrix_event_base, JsonNode *json_data, GError **error) +{ + MatrixEventBasePrivate *priv = matrix_event_base_get_instance_private(matrix_event_base); + JsonObject *root; + JsonNode *node; + const gchar *json_event_type; + GError *inner_error = NULL; + + g_return_if_fail(matrix_event_base != NULL); + + if (json_node_get_node_type(json_data) != JSON_NODE_OBJECT) { + g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_INVALID_FORMAT, + "The event is not valid"); + + return; + } + + root = json_node_get_object(json_data); + + if ((node = json_object_get_member(root, "type")) == NULL) { + g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_INCOMPLETE, + "Event type is not specified"); + + return; + } + + json_event_type = json_node_get_string(node); + + if ((priv->_event_type != NULL) && (g_strcmp0(priv->_event_type, json_event_type) != 0)) { + g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_INVALID_TYPE, + "Changing event type is not supported"); + + return; + } + + if ((node = json_object_get_member(root, "content")) == NULL) { + g_warning("content key is missing from an %s event", json_event_type); + + // As event type objects depend on having this node, let’s add it now. + JsonNode *content_node = json_node_new(JSON_NODE_OBJECT); + json_node_set_object(content_node, json_object_new()); + json_object_set_member(root, "content", content_node); + } + + matrix_event_base_from_json(matrix_event_base, json_data, &inner_error); + + if (inner_error != NULL) { + g_propagate_error(error, inner_error); + + return; + } + + g_free(priv->_event_type); + priv->_event_type = g_strdup(json_event_type); +} + +static void +matrix_event_base_real_from_json(MatrixEventBase *matrix_event_base, JsonNode *json_data, GError **error) +{ + MatrixEventBasePrivate *priv = matrix_event_base_get_instance_private(matrix_event_base); + JsonObject *root; + JsonNode *node; + + g_return_if_fail(matrix_event_base != NULL); + + root = json_node_get_object(json_data); + + if ((node = json_object_get_member(root, "type")) != NULL) { + g_free(priv->_event_type); + priv->_event_type = g_strdup(json_node_get_string(node)); + } else if (DEBUG) { + g_warning("type is not present in an event"); + } +} + +/** + * matrix_event_base_from_json: + * @event: a #MatrixEventBase (or derived) object + * @json_data: a #JsonNode to load data from. It must hold a #JsonObject + * @error: a #GError, or %NULL to ignore errors + * + * Load data from a JSON object to the fields of @event. + */ +void +matrix_event_base_from_json(MatrixEventBase *matrix_event_base, JsonNode *json_data, GError **error) +{ + g_return_if_fail(matrix_event_base != NULL); + g_return_if_fail(json_data != NULL); + g_return_if_fail(json_node_get_node_type(json_data) != JSON_NODE_OBJECT); + + MATRIX_EVENT_BASE_GET_CLASS(matrix_event_base)->from_json(matrix_event_base, json_data, error); +} + +static void +matrix_event_base_real_to_json(MatrixEventBase *matrix_event_base, JsonNode *json_data, GError **error) +{ + MatrixEventBasePrivate *priv = matrix_event_base_get_instance_private(matrix_event_base); + JsonObject *root; + + g_return_if_fail(matrix_event_base != NULL); + + root = json_node_get_object(json_data); + + if (priv->_event_type == NULL) { + g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_INCOMPLETE, + "Won't generate an event without type"); + + return; + } + + json_object_set_string_member(root, "type", priv->_event_type); +} + +/** + * matrix_event_base_to_json: + * @event: a #MatrixEventBase (or derived) object + * @json_data: a #JsonNode initialised to hold a #JsonObject + * @error: a #GError, or %NULL to ignore errors + * + * Export event data to a JSON object. + */ +void +matrix_event_base_to_json(MatrixEventBase *matrix_event_base, JsonNode *json_data, GError **error) +{ + g_return_if_fail(matrix_event_base != NULL); + g_return_if_fail(json_data != NULL); + g_return_if_fail(json_node_get_node_type(json_data) != JSON_NODE_OBJECT); + + MATRIX_EVENT_BASE_GET_CLASS(matrix_event_base)->to_json(matrix_event_base, json_data, error); +} + +/** + * matrix_event_base_new_from_json: + * @event_type: (nullable) (transfer none): an event type + * @json_data: (not nullable) (transfer none): a #JsonNode, holding a #JsonObject + * @error: (nullable): a #GError, or %NULL to ignore errors + * + * Create a new #MatrixEventBase derived object based on @event_type. If @event_type is %NULL, + * the event type is taken directly from the JSON data, namely the `"event_type"` field. + * + * After figuring out the event type (either from @event_type or from the event itself), this + * function calls matrix_event_get_handler() to get the handler #GType for this event. If + * none found, @error is set to #MATRIX_ERROR_INVALID_TYPE, and this function returns %NULL. + * + * The actual return type of this function is the same as the handling class (which is + * required to be a subclass of #MatrixEventBase). + * + * When object initialisation is done, matrix_event_base_from_json() is called to populate + * the event object. + * + * Returns: (transfer full): a new #MatrixEventBase devired object + */ +MatrixEventBase * +matrix_event_base_new_from_json(const gchar *event_type, JsonNode *json_data, GError **error) +{ + GType event_gtype; + MatrixEventBase *ret = NULL; + GError *inner_error = NULL; + + if (event_type == NULL) { + JsonNode *node; + + if (json_data == NULL) { + g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_INCOMPLETE, + "Either event_type or json_data must be set!"); + + return NULL; + } + + if (json_node_get_node_type(json_data) != JSON_NODE_OBJECT) { + g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_INVALID_FORMAT, + "Event is not a JSON object!"); + + return NULL; + } + + if ((node = json_object_get_member(json_node_get_object(json_data), "type")) == NULL) { + g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_INCOMPLETE, + "event_type is null and JSON object doesn't contain type!"); + + return NULL; + } + + event_type = (gchar *)json_node_get_string(node); + } + + if ((event_gtype = matrix_event_get_handler(event_type)) == G_TYPE_NONE) { + g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_INVALID_TYPE, + "No registered type for event type %s", + event_type); + + return NULL; + } + + ret = (MatrixEventBase *)g_object_new(event_gtype, + "event_type", event_type, + "json", json_data); + + matrix_event_base_initable_init(G_INITABLE(ret), NULL, &inner_error); + + if (inner_error != NULL) { + g_propagate_error(error, inner_error); + + return NULL; + } + + return ret; +} + +/** + * matrix_event_base_construct: + * @object_type: a #GType to construct + * + * Returns: (transfer full): a new instance of @object_type + */ +MatrixEventBase * +matrix_event_base_construct(GType object_type) +{ + return (MatrixEventBase *)g_object_new(object_type, NULL); +} + +/** + * matrix_event_base_get_event_type: + * @event: a #MatrixEventBase (or derived) object + * + * Get the event type of @event. + * + * The returned value is owned by @event and should not be freed. + * + * Returns: (transfer none): the event type + */ +const gchar * +matrix_event_base_get_event_type(MatrixEventBase *matrix_event_base) +{ + MatrixEventBasePrivate *priv = matrix_event_base_get_instance_private(matrix_event_base); + + g_return_val_if_fail(matrix_event_base != NULL, NULL); + + return priv->_event_type; +} + +static void +matrix_event_base_set_event_type(MatrixEventBase *matrix_event_base, const gchar *event_type) +{ + MatrixEventBasePrivate *priv = matrix_event_base_get_instance_private(matrix_event_base); + + g_return_if_fail(matrix_event_base != NULL); + + g_free(priv->_event_type); + priv->_event_type = g_strdup (event_type); + + g_object_notify_by_pspec((GObject *)matrix_event_base, + matrix_event_base_properties[PROP_EVENT_TYPE]); +} + +JsonNode * +matrix_event_base_get_json(MatrixEventBase *matrix_event_base) +{ + MatrixEventBasePrivate *priv; + JsonNode *result; + JsonNode *content_node; + JsonObject *root; + JsonObject *content_root; + GError *inner_error = NULL; + + g_return_val_if_fail(matrix_event_base != NULL, NULL); + + priv = matrix_event_base_get_instance_private(matrix_event_base); + + result = json_node_new (JSON_NODE_OBJECT); + root = json_object_new(); + json_node_set_object(priv->_json, root); + + content_node = json_node_new(JSON_NODE_OBJECT); + content_root = json_object_new(); + json_node_set_object(content_node, content_root); + json_object_set_member(root, "content", content_node); + + matrix_event_base_to_json(matrix_event_base, priv->_json, &inner_error); + + if (inner_error != NULL) { + g_error("Unable to generate JSON content: %s", inner_error->message); + g_error_free(inner_error); + g_object_unref(result); + + return NULL; + } + + json_node_unref(priv->_json); + + priv->_json = result; + + return priv->_json; +} + +static void +matrix_event_base_set_json(MatrixEventBase *matrix_event_base, JsonNode *json) +{ + GError* inner_error = NULL; + + g_return_if_fail(matrix_event_base != NULL); + + if (json != NULL) { + matrix_event_base_initialize_from_json(matrix_event_base, json, &inner_error); + + if (inner_error != NULL) { + g_error("Unable to initialise from JSON data: %s", inner_error->message); + g_error_free(inner_error); + + return; + } + } + + g_object_notify_by_pspec((GObject *)matrix_event_base, matrix_event_base_properties[PROP_JSON]); +} + +static void +matrix_event_base_get_property(GObject *gobject, guint prop_id, GValue *value, GParamSpec *pspec) +{ + MatrixEventBase *matrix_event_base = MATRIX_EVENT_BASE(gobject); + + switch (prop_id) { + case PROP_EVENT_TYPE: + g_value_set_string(value, matrix_event_base_get_event_type(matrix_event_base)); + + break; + case PROP_JSON: + g_value_set_boxed(value, matrix_event_base_get_json(matrix_event_base)); + + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec); + + break; + } +} + +static void +matrix_event_base_set_property(GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + MatrixEventBase *matrix_event_base = MATRIX_EVENT_BASE(gobject); + + switch (prop_id) { + case PROP_EVENT_TYPE: + matrix_event_base_set_event_type(matrix_event_base, g_value_get_string(value)); + + break; + case PROP_JSON: + matrix_event_base_set_json(matrix_event_base, g_value_get_boxed(value)); + + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec); + + break; + } +} + +static void +matrix_event_base_finalize(GObject *gobject) +{ + MatrixEventBase *matrix_event_base = MATRIX_EVENT_BASE(gobject); + MatrixEventBasePrivate *priv = matrix_event_base_get_instance_private(matrix_event_base); + + if (priv->_construct_error) { + g_error_free(priv->_construct_error); + } + + priv->_json = (json_node_unref(priv->_json), NULL); + + g_free(priv->_event_type); + + G_OBJECT_CLASS(matrix_event_base_parent_class)->finalize(gobject); +} + +static void +matrix_event_base_class_init(MatrixEventBaseClass *klass) +{ + ((MatrixEventBaseClass *)klass)->from_json = matrix_event_base_real_from_json; + ((MatrixEventBaseClass *)klass)->to_json = matrix_event_base_real_to_json; + + G_OBJECT_CLASS(klass)->get_property = matrix_event_base_get_property; + G_OBJECT_CLASS(klass)->set_property = matrix_event_base_set_property; + G_OBJECT_CLASS(klass)->finalize = matrix_event_base_finalize; + + /** + * MatrixEventBase:event-type: + * + * The type of the event. It should be namespaced similar to the Java package naming + * conventions, e.g. `com.example.subdomain.event.type`. It cannot be changed after object + * initialization. + */ + matrix_event_base_properties[PROP_EVENT_TYPE] = g_param_spec_string( + "event-type", "event-type", "event-type", + NULL, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property(G_OBJECT_CLASS(klass), + PROP_EVENT_TYPE, + matrix_event_base_properties[PROP_EVENT_TYPE]); + + /** + * MatrixEventBase:json: + * + * The event as a JSON node. + */ + matrix_event_base_properties[PROP_JSON] = g_param_spec_boxed( + "json", "json", "json", + JSON_TYPE_NODE, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property(G_OBJECT_CLASS(klass), + PROP_JSON, + matrix_event_base_properties[PROP_JSON]); +} + +static void +matrix_event_base_g_initable_interface_init(GInitableIface *iface) +{ + iface->init = matrix_event_base_initable_init; +} + +static void +matrix_event_base_init(MatrixEventBase *matrix_event_base) +{ + MatrixEventBasePrivate *priv = matrix_event_base_get_instance_private(matrix_event_base); + + priv->_construct_error = NULL; + priv->_inited = FALSE; + priv->_event_type = NULL; + + matrix_event_base_set_event_type(matrix_event_base, NULL); +} + +/** + * matrix_event_get_handler: + * @event_type: (transfer none) (not nullable): the event type to look up + * + * Get the #GType of the class that is registered to handle events with type @event_type. + * + * Returns: a #GType, or #G_TYPE_NONE if no handler is registered + */ +GType +matrix_event_get_handler(const gchar *event_type) +{ + GTypeClass *klass; + + g_return_val_if_fail(event_type != NULL, G_TYPE_NONE); + + if ((klass = (GTypeClass *)g_hash_table_lookup(matrix_event_type_handlers, event_type)) != NULL) { + return G_TYPE_FROM_CLASS(klass); + } + + if (DEBUG) { + g_warning ("matrix-event-base.vala:243: No registered type for %s", event_type); + } + + return G_TYPE_NONE; +} + +/** + * matrix_event_register_type: + * @event_type: (transfer none): the type of the event + * @event_gtype: the #GType of the event’s handler + * @error: a #GError, or %NULL to ignore errors + * + * Registers @event_type to be handled by the type @event_gtype. + */ +void +matrix_event_register_type(const gchar *event_type, GType event_gtype, GError **error) +{ + gchar *key; + GTypeClass *klass; + + g_return_if_fail(event_type != NULL); + + if (!g_type_is_a (event_gtype, MATRIX_EVENT_TYPE_BASE)) { + g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_INVALID_TYPE, + "Invalid event type handler. It must be a subclass of MatrixEvent"); + + return; + } + + if (matrix_event_type_handlers == NULL) { + matrix_event_type_handlers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_type_class_unref); + } + + key = g_strdup (event_type); + klass = g_type_class_ref (event_gtype); + + g_hash_table_replace(matrix_event_type_handlers, key, klass); +} + +/** + * matrix_event_unregister_type: + * @event_type: (transfer none): the event type to remove + * + * Unregister @param event_type. + */ +void +matrix_event_unregister_type(const gchar *event_type) +{ + g_return_if_fail(event_type != NULL && matrix_event_type_handlers != NULL); + + g_hash_table_remove (matrix_event_type_handlers, event_type); +} diff --git a/src/matrix-event-base.h b/src/matrix-event-base.h new file mode 100644 index 0000000..1d5425f --- /dev/null +++ b/src/matrix-event-base.h @@ -0,0 +1,59 @@ +/* + * 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 + * . + */ + +#ifndef __MATRIX_GLIX_SDK_EVENT_BASE_H__ +# define __MATRIX_GLIX_SDK_EVENT_BASE_H__ + +# include +# include + +# define MATRIX_EVENT_TYPE_BASE (matrix_event_base_get_type ()) +# define MATRIX_EVENT_BASE(o) (G_TYPE_CHECK_INSTANCE_CAST((o), MATRIX_EVENT_TYPE_BASE, MatrixEventBase)) +# define MATRIX_EVENT_BASE_CLASS(c) (G_TYPE_CHECK_CLASS_CAST((c), MATRIX_EVENT_TYPE_BASE, MatrixEventBaseClass)) +# define MATRIX_EVENT_IS_BASE(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), MATRIX_EVENT_TYPE_BASE)) +# define MATRIX_EVENT_IS_BASE_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE((c), MATRIX_EVENT_TYPE_BASE)) +# define MATRIX_EVENT_BASE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), MATRIX_EVENT_TYPE_BASE, MatrixEventBaseClass)) + +typedef struct _MatrixEventBase MatrixEventBase; +typedef struct _MatrixEventBaseClass MatrixEventBaseClass; +G_DEFINE_AUTOPTR_CLEANUP_FUNC(MatrixEventBase, g_object_unref) + +struct _MatrixEventBase { + GObject parent_instance; +}; + +struct _MatrixEventBaseClass { + GObjectClass parent_instance; + + void (*from_json)(MatrixEventBase *event, JsonNode *json_data, GError **error); + void (*to_json)(MatrixEventBase *event, JsonNode *json_data, GError **error); +}; + +GType matrix_event_get_handler(const gchar *event_type); +void matrix_event_register_type(const gchar *event_type, GType event_gtype, GError **error); +void matrix_event_unregister_type(const gchar *event_type); + +GType matrix_event_base_get_type(void) G_GNUC_CONST; +void matrix_event_base_from_json(MatrixEventBase *event, JsonNode *json_data, GError **error); +void matrix_event_base_to_json(MatrixEventBase *event, JsonNode *json_data, GError **error); +MatrixEventBase *matrix_event_base_new_from_json(const gchar *event_type, JsonNode *json_data, GError **error); +MatrixEventBase *matrix_event_base_construct(GType object_type); +const gchar *matrix_event_base_get_event_type(MatrixEventBase *event); +JsonNode *matrix_event_base_get_json(MatrixEventBase *event); + +#endif /* __MATRIX_GLIX_SDK_EVENT_BASE_H__ */ diff --git a/src/matrix-event-base.vala b/src/matrix-event-base.vala deleted file mode 100644 index e707dc8..0000000 --- a/src/matrix-event-base.vala +++ /dev/null @@ -1,285 +0,0 @@ -/* - * 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 - * . - */ - -/** - * Base class for Matrix events. - */ -public abstract class Matrix.Event.Base : GLib.Object, GLib.Initable { - private Error? _construct_error = null; - private bool _inited = false; - private Json.Node? _json; - - protected string? _event_type = null; - - /** - * The type of the event. It should be namespaced similar to the - * Java package naming conventions, - * e.g. `com.example.subdomain.event.type`. It cannot be changed - * after object initialization. - */ - public string? event_type { - get { - return _event_type; - } - - construct { - _event_type = value; - } - - default = null; - } - - /** - * The event as a JSON node. - */ - public Json.Node? json { - get { - _json = new Json.Node(Json.NodeType.OBJECT); - _json.set_object(new Json.Object()); - - // Add the content node, as all event types must include it. - var content_root = new Json.Node(Json.NodeType.OBJECT); - var content_obj = new Json.Object(); - content_root.set_object(content_obj); - _json.get_object().set_member("content", content_root); - - try { - to_json(_json); - } - catch (Matrix.Error e) { - return null; - } - - return _json; - } - - construct { - if (value != null) { - try { - initialize_from_json(value); - } catch (Matrix.Error e) {} - } - } - } - - public bool - init(GLib.Cancellable? cancellable = null) - throws Error, Matrix.Error - { - if (cancellable != null) { - throw new Matrix.Error.UNSUPPORTED( - "Cancellable initialization not supported"); - } - - if (_construct_error != null) { - throw _construct_error; - } - - _inited = true; - - return true; - } - - private void - initialize_from_json(Json.Node json_data) - throws Matrix.Error - { - Json.Object root; - Json.Node node; - - if (json_data.get_node_type() != Json.NodeType.OBJECT) { - throw new Matrix.Error.INVALID_FORMAT( - "The event is not valid"); - } - - root = json_data.get_object(); - - if ((node = root.get_member("type")) == null) { - throw new Matrix.Error.INCOMPLETE( - "Event type is not specified"); - } - - if ((_event_type != null) - && (_event_type != node.get_string())) { - throw new Matrix.Error.INVALID_TYPE( - "Changing event type is not supported"); - } - - var evt_type = node.get_string(); - - if ((node = root.get_member("content")) == null) { - warning("content key is missing from the %s event", evt_type); - - // As event type objects depend on having this node, let’s - // add it now. - var content_node = new Json.Node(Json.NodeType.OBJECT); - content_node.set_object(new Json.Object()); - root.set_member("content", content_node); - } - - from_json(json_data); - - _event_type = evt_type; - } - - /** - * Subclasses should implement this function to initialize - * themselves from JSON data. - */ - public virtual void - from_json(Json.Node json_data) - throws Matrix.Error - { - var root = json_data.get_object(); - Json.Node? node; - - if ((node = root.get_member("type")) != null) { - _event_type = node.get_string(); - } else if (Config.DEBUG) { - warning("type is not present in an event"); - } - } - - /** - * Subclasses should implement this to export their data to JSON. - */ - public virtual void - to_json(Json.Node json_data) - throws Matrix.Error - { - var root = json_data.get_object(); - - if (_event_type == null) { - throw new Matrix.Error.INCOMPLETE( - "Won't generate an event without type"); - } - - root.set_string_member("type", _event_type); - } - - public static Base? - new_from_json(owned string? event_type = null, - Json.Node? json_data = null) - throws Matrix.Error, GLib.Error - { - GLib.Type? event_gtype; - Base? ret = null; - - if (event_type == null) { - if (json_data == null) { - throw new Matrix.Error.INCOMPLETE( - "Either event_type or json_data must be set!"); - } - - if (json_data.get_node_type() != Json.NodeType.OBJECT) { - throw new Matrix.Error.INVALID_FORMAT( - "Event is not a JSON object!"); - } - - Json.Node? node; - - if ((node = json_data.get_object().get_member("type")) == null) { - throw new Matrix.Error.INCOMPLETE( - "event_type is null and JSON object doesn't contain type!"); - } - - event_type = node.get_string(); - } - - if ((event_gtype = get_handler(event_type)) == null) { - throw new Matrix.Error.INVALID_TYPE( - "No registered type for event type %s", - event_type); - } - - ret = (Base)Object.new(event_gtype, - event_type : event_type, - json : json_data); - - ret.init(); - - return ret; - } -} - -namespace Matrix.Event { - -private HashTable? type_handlers = null; - -/** - * Get the {@link GLib.Type} of the class that is registered to - * handle events with type @param event_type. - * - * @param event_type the event type to look up - * @return a {@link GLib.Type} or {@link Matrix.Event} if no - * handler is registered - */ -public static GLib.Type? -get_handler(string event_type) -{ - unowned GLib.TypeClass? klass = null; - - if ((type_handlers != null) - && ((klass = type_handlers.get(event_type)) != null)) { - return klass.get_type(); - } - - if (Config.DEBUG) { - warning("No registered type for %s", event_type); - } - - return null; -} - -/** - * Registers @param event_type to be handled by the - * type @param event_gtype. - * - * @param event_type the type of the event - * @param event_gtype the {@link GLib.Type} of the event’s handler - */ -public static void -register_type(string event_type, GLib.Type event_gtype) - throws Matrix.Error -{ - if (!event_gtype.is_a(typeof(Matrix.Event.Base))) { - throw new Matrix.Error.INVALID_TYPE( - "Invalid event type handler. It must be a subclass of MatrixEvent"); - } - - if (type_handlers == null) { - type_handlers = new HashTable( - str_hash, str_equal); - } - - type_handlers.replace(event_type, event_gtype.class_ref()); -} - -/** - * Unregister @param event_type. - * - * @param event_type the event type to remove - */ -public static void -unregister_type(string event_type) -{ - if (type_handlers != null) { - type_handlers.remove(event_type); - } -} -} diff --git a/vapi/c-api.vapi b/vapi/c-api.vapi index 6ba2089..0c7bcae 100644 --- a/vapi/c-api.vapi +++ b/vapi/c-api.vapi @@ -456,4 +456,38 @@ namespace Matrix { public bool glib_check_version(uint required_major, uint required_minor, uint required_micro); + + [CCode (gir_namespace = "MatrixEvent", gir_version = "0.0")] + namespace Event { + [CCode (cheader_filename = "matrix-event-base.h")] + public abstract class Base : GLib.Object, GLib.Initable { + protected string? _event_type; + public string? event_type { get; construct; default = null; } + public Json.Node? json { get; set; default = null; } + + public Base(); + + public bool init(GLib.Cancellable? cancellable = null) + throws Error, Matrix.Error; + + public virtual void from_json(Json.Node json_data) + throws Matrix.Error; + + public virtual void to_json(Json.Node json_data) + throws Matrix.Error; + + public static Base? new_from_json(owned string? event_type = null, Json.Node? json_data = null) + throws Matrix.Error, GLib.Error; + } + + [CCode (cheader_filename = "matrix-event-base.h")] + public static GLib.Type? get_handler(string event_type); + + [CCode (cheader_filename = "matrix-event-base.h")] + public static void register_type(string event_type, GLib.Type event_gtype) + throws Matrix.Error; + + [CCode (cheader_filename = "matrix-event-base.h")] + public static void unregister_type(string event_type); + } }