/* * wocky-pep-service.c - WockyPepService * Copyright © 2009,2012 Collabora Ltd. * * This library 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 2.1 of the License, or (at your option) any later version. * * This library 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 this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /** * SECTION: wocky-pep-service * @title: WockyPepService * @short_description: Object to represent a single PEP service * @include: wocky/wocky-pep-service.h * * Object to aid with looking up PEP nodes and listening for changes. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "wocky-pep-service.h" #include "wocky-pubsub-helpers.h" #include "wocky-porter.h" #include "wocky-utils.h" #include "wocky-namespaces.h" #include "wocky-signals-marshal.h" #define WOCKY_DEBUG_FLAG WOCKY_DEBUG_PUBSUB #include "wocky-debug-internal.h" G_DEFINE_TYPE (WockyPepService, wocky_pep_service, G_TYPE_OBJECT) /* signal enum */ enum { CHANGED, LAST_SIGNAL, }; static guint signals[LAST_SIGNAL] = {0}; enum { PROP_NODE = 1, PROP_SUBSCRIBE, }; /* private structure */ struct _WockyPepServicePrivate { WockySession *session; WockyPorter *porter; WockyContactFactory *contact_factory; gchar *node; gboolean subscribe; guint handler_id; gboolean dispose_has_run; }; static void wocky_pep_service_init (WockyPepService *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, WOCKY_TYPE_PEP_SERVICE, WockyPepServicePrivate); } static void wocky_pep_service_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { WockyPepService *self = WOCKY_PEP_SERVICE (object); WockyPepServicePrivate *priv = self->priv; switch (property_id) { case PROP_NODE: priv->node = g_value_dup_string (value); break; case PROP_SUBSCRIBE: priv->subscribe = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void wocky_pep_service_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { WockyPepService *self = WOCKY_PEP_SERVICE (object); WockyPepServicePrivate *priv = self->priv; switch (property_id) { case PROP_NODE: g_value_set_string (value, priv->node); break; case PROP_SUBSCRIBE: g_value_set_boolean (value, priv->subscribe); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void wocky_pep_service_dispose (GObject *object) { WockyPepService *self = WOCKY_PEP_SERVICE (object); WockyPepServicePrivate *priv = self->priv; if (priv->dispose_has_run) return; priv->dispose_has_run = TRUE; if (priv->porter != NULL) { g_assert (priv->handler_id != 0); wocky_porter_unregister_handler (priv->porter, priv->handler_id); g_object_unref (priv->porter); } if (priv->contact_factory != NULL) g_object_unref (priv->contact_factory); if (G_OBJECT_CLASS (wocky_pep_service_parent_class)->dispose) G_OBJECT_CLASS (wocky_pep_service_parent_class)->dispose (object); } static void wocky_pep_service_finalize (GObject *object) { WockyPepService *self = WOCKY_PEP_SERVICE (object); WockyPepServicePrivate *priv = self->priv; g_free (priv->node); G_OBJECT_CLASS (wocky_pep_service_parent_class)->finalize (object); } static void wocky_pep_service_constructed (GObject *object) { WockyPepService *self = WOCKY_PEP_SERVICE (object); WockyPepServicePrivate *priv = self->priv; g_assert (priv->node != NULL); } static void wocky_pep_service_class_init (WockyPepServiceClass *wocky_pep_service_class) { GObjectClass *object_class = G_OBJECT_CLASS (wocky_pep_service_class); GParamSpec *param_spec; g_type_class_add_private (wocky_pep_service_class, sizeof (WockyPepServicePrivate)); object_class->set_property = wocky_pep_service_set_property; object_class->get_property = wocky_pep_service_get_property; object_class->dispose = wocky_pep_service_dispose; object_class->finalize = wocky_pep_service_finalize; object_class->constructed = wocky_pep_service_constructed; /** * WockyPepService:node: * * Namespace of the PEP node. */ param_spec = g_param_spec_string ("node", "node", "namespace of the pep node", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_NODE, param_spec); /** * WockyPepService:subscribe: * * %TRUE if Wocky is to subscribe to the notifications of the node. */ param_spec = g_param_spec_boolean ("subscribe", "subscribe", "if TRUE, Wocky will subscribe to the notifications of the node", FALSE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_SUBSCRIBE, param_spec); /** * WockyPepService::changed: * @self: a #WockyPepService object * @contact: the #WockyBareContact who changed the node * @stanza: the #WockyStanza * @item: the first—and typically only—<item> element in @stanza, or * %NULL if there is none. * * Emitted when the node value changes. */ signals[CHANGED] = g_signal_new ("changed", G_OBJECT_CLASS_TYPE (wocky_pep_service_class), G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0, NULL, NULL, _wocky_signals_marshal_VOID__OBJECT_OBJECT_POINTER, G_TYPE_NONE, 3, WOCKY_TYPE_BARE_CONTACT, WOCKY_TYPE_STANZA, G_TYPE_POINTER); } /** * wocky_pep_service_new: * @node: the namespace of the PEP node * @subscribe: %TRUE if Wocky is to subscribe to the notifications of * the node, otherwise %FALSE * * A convenience function to create a new #WockyPepService object. * * Returns: a new #WockyPepService */ WockyPepService * wocky_pep_service_new (const gchar *node, gboolean subscribe) { return g_object_new (WOCKY_TYPE_PEP_SERVICE, "node", node, "subscribe", subscribe, NULL); } static gboolean msg_event_cb (WockyPorter *porter, WockyStanza *stanza, gpointer user_data) { WockyPepService *self = WOCKY_PEP_SERVICE (user_data); WockyPepServicePrivate *priv = self->priv; const gchar *from; WockyBareContact *contact; WockyStanzaSubType sub_type; WockyNode *event, *items, *item; from = wocky_stanza_get_from (stanza); if (from == NULL) { DEBUG ("No 'from' attribute; ignoring event"); return FALSE; } wocky_stanza_get_type_info (stanza, NULL, &sub_type); /* type of the message is supposed to be 'headline' but old ejabberd * omits it */ if (sub_type != WOCKY_STANZA_SUB_TYPE_NONE && sub_type != WOCKY_STANZA_SUB_TYPE_HEADLINE) { return FALSE; } event = wocky_node_get_child_ns (wocky_stanza_get_top_node (stanza), "event", WOCKY_XMPP_NS_PUBSUB_EVENT); g_return_val_if_fail (event != NULL, FALSE); items = wocky_node_get_child (event, "items"); g_return_val_if_fail (items != NULL, FALSE); item = wocky_node_get_child (items, "item"); contact = wocky_contact_factory_ensure_bare_contact ( priv->contact_factory, from); g_signal_emit (G_OBJECT (self), signals[CHANGED], 0, contact, stanza, item); g_object_unref (contact); return TRUE; } /** * wocky_pep_service_start: * @self: a #WockyPepService object * @session: a #WockySession object * * Start listening to the PEP node @node and signal changes by using * #WockyPepService::changed. */ void wocky_pep_service_start (WockyPepService *self, WockySession *session) { WockyPepServicePrivate *priv = self->priv; g_assert (priv->session == NULL); priv->session = session; priv->porter = wocky_session_get_porter (priv->session); g_object_ref (priv->porter); priv->contact_factory = wocky_session_get_contact_factory (priv->session); g_object_ref (priv->contact_factory); /* Register event handler */ priv->handler_id = wocky_porter_register_handler_from_anyone (priv->porter, WOCKY_STANZA_TYPE_MESSAGE, WOCKY_STANZA_SUB_TYPE_NONE, WOCKY_PORTER_HANDLER_PRIORITY_MAX, msg_event_cb, self, '(', "event", ':', WOCKY_XMPP_NS_PUBSUB_EVENT, '(', "items", '@', "node", priv->node, ')', ')', NULL); /* TODO: subscribe to node if needed */ } static void send_query_cb (GObject *source, GAsyncResult *res, gpointer user_data) { GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (user_data); GError *error = NULL; WockyStanza *reply; reply = wocky_porter_send_iq_finish (WOCKY_PORTER (source), res, &error); if (reply == NULL) { g_simple_async_result_set_from_error (result, error); g_error_free (error); } else { g_simple_async_result_set_op_res_gpointer (result, reply, g_object_unref); } g_simple_async_result_complete (result); g_object_unref (result); } /** * wocky_pep_service_get_async: * @self: a #WockyPepService object * @contact: a #WockyBareContact object * @cancellable: an optional #GCancellable object, or %NULL * @callback: a function to call when the node is retrieved * @user_data: user data for @callback * * Starts an asynchronous operation to get the PEP node, * #WockyPepService:node. * * When the operation is complete, @callback will be called and the * function should call wocky_pep_service_get_finish(). */ void wocky_pep_service_get_async (WockyPepService *self, WockyBareContact *contact, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { WockyPepServicePrivate *priv = self->priv; WockyStanza *msg; GSimpleAsyncResult *result; const gchar *jid; if (priv->porter == NULL) { g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data, WOCKY_PORTER_ERROR, WOCKY_PORTER_ERROR_NOT_STARTED, "Service has not been started"); return; } jid = wocky_bare_contact_get_jid (contact); msg = wocky_stanza_build ( WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_GET, NULL, jid, '(', "pubsub", ':', WOCKY_XMPP_NS_PUBSUB, '(', "items", '@', "node", priv->node, ')', ')', NULL); result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, wocky_pep_service_get_async); wocky_porter_send_iq_async (priv->porter, msg, cancellable, send_query_cb, result); g_object_unref (msg); } /** * wocky_pep_service_get_finish: * @self: a #WockyPepService object * @result: a #GAsyncResult * @item: (out) (allow-none): on success, the first <item> element * in the result, or %NULL if @self has no published items. * @error: a location to store a #GError if an error occurs * * Finishes an asynchronous operation to get the PEP node, * #WockyPepService:node. For more details, see * wocky_pep_service_get_async(). * * Returns: the #WockyStanza retrieved from getting the PEP node. */ WockyStanza * wocky_pep_service_get_finish (WockyPepService *self, GAsyncResult *result, WockyNode **item, GError **error) { GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); WockyStanza *reply; if (g_simple_async_result_propagate_error (simple, error)) return NULL; g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self), wocky_pep_service_get_async), NULL); reply = WOCKY_STANZA (g_simple_async_result_get_op_res_gpointer (simple)); if (item != NULL) { WockyNode *pubsub_node = wocky_node_get_child_ns ( wocky_stanza_get_top_node (reply), "pubsub", WOCKY_XMPP_NS_PUBSUB); WockyNode *items_node = NULL; if (pubsub_node != NULL) items_node = wocky_node_get_child (pubsub_node, "items"); if (items_node != NULL) *item = wocky_node_get_child (items_node, "item"); else *item = NULL; } return g_object_ref (reply); } /** * wocky_pep_service_make_publish_stanza: * @self: a #WockyPepService * @item: a location to store the item #WockyNode, or %NULL * * Generates a new IQ type='set' PEP publish stanza. * * Returns: a new #WockyStanza PEP publish stanza; free with g_object_unref() */ WockyStanza * wocky_pep_service_make_publish_stanza (WockyPepService *self, WockyNode **item) { WockyPepServicePrivate *priv = self->priv; return wocky_pubsub_make_publish_stanza (NULL, priv->node, NULL, NULL, item); }