/* * text-channel.h - high level API for Text channels * * Copyright (C) 2010 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:text-channel * @title: TpTextChannel * @short_description: proxy object for a text channel * * #TpTextChannel is a sub-class of #TpChannel providing convenient API * to send and receive #TpMessage. */ /** * TpTextChannel: * * Data structure representing a #TpTextChannel. * * Since: 0.13.10 */ /** * TpTextChannelClass: * * The class of a #TpTextChannel. * * Since: 0.13.10 */ #include "config.h" #include "telepathy-glib/text-channel.h" #include #include #include #include #include #include #include #include #include #include #include #include #define DEBUG_FLAG TP_DEBUG_CHANNEL #include "telepathy-glib/debug-internal.h" #include "telepathy-glib/automatic-client-factory-internal.h" #include "telepathy-glib/channel-internal.h" #include #include G_DEFINE_TYPE (TpTextChannel, tp_text_channel, TP_TYPE_CHANNEL) struct _TpTextChannelPrivate { GStrv supported_content_types; TpMessagePartSupportFlags message_part_support_flags; TpDeliveryReportingSupportFlags delivery_reporting_support; GArray *message_types; GSimpleAsyncResult *pending_messages_result; guint n_preparing_pending_messages; /* queue of owned TpSignalledMessage */ GQueue *pending_messages; gboolean got_initial_messages; gboolean is_sms_channel; gboolean sms_flash; }; enum { PROP_SUPPORTED_CONTENT_TYPES = 1, PROP_MESSAGE_PART_SUPPORT_FLAGS, PROP_DELIVERY_REPORTING_SUPPORT, PROP_MESSAGE_TYPES, PROP_IS_SMS_CHANNEL, PROP_SMS_FLASH, }; enum /* signals */ { SIG_MESSAGE_RECEIVED, SIG_PENDING_MESSAGE_REMOVED, SIG_MESSAGE_SENT, SIG_CONTACT_CHAT_STATE_CHANGED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0, }; static void tp_text_channel_dispose (GObject *obj) { TpTextChannel *self = (TpTextChannel *) obj; tp_clear_pointer (&self->priv->supported_content_types, g_strfreev); tp_clear_pointer (&self->priv->message_types, g_array_unref); g_queue_foreach (self->priv->pending_messages, (GFunc) g_object_unref, NULL); tp_clear_pointer (&self->priv->pending_messages, g_queue_free); G_OBJECT_CLASS (tp_text_channel_parent_class)->dispose (obj); } static void tp_text_channel_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { TpTextChannel *self = (TpTextChannel *) object; switch (property_id) { case PROP_SUPPORTED_CONTENT_TYPES: g_value_set_boxed (value, tp_text_channel_get_supported_content_types (self)); break; case PROP_MESSAGE_PART_SUPPORT_FLAGS: g_value_set_uint (value, tp_text_channel_get_message_part_support_flags (self)); break; case PROP_DELIVERY_REPORTING_SUPPORT: g_value_set_uint (value, tp_text_channel_get_delivery_reporting_support (self)); break; case PROP_MESSAGE_TYPES: g_value_set_boxed (value, tp_text_channel_get_message_types (self)); break; case PROP_IS_SMS_CHANNEL: g_value_set_boolean (value, tp_text_channel_is_sms_channel (self)); break; case PROP_SMS_FLASH: g_value_set_boolean (value, tp_text_channel_get_sms_flash (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static TpHandle get_sender (TpTextChannel *self, const GPtrArray *message, TpContact **contact, const gchar **out_sender_id) { const GHashTable *header; TpHandle handle; const gchar *sender_id = NULL; TpConnection *conn; g_assert (contact != NULL); header = g_ptr_array_index (message, 0); handle = tp_asv_get_uint32 (header, "message-sender", NULL); if (handle == 0) { DEBUG ("Message received on Channel %s doesn't have message-sender", tp_proxy_get_object_path (self)); *contact = NULL; goto out; } sender_id = tp_asv_get_string (header, "message-sender-id"); conn = tp_channel_get_connection ((TpChannel *) self); *contact = tp_connection_dup_contact_if_possible (conn, handle, sender_id); if (*contact == NULL) { if (!tp_connection_has_immortal_handles (conn)) DEBUG ("Connection %s don't have immortal handles, please fix CM", tp_proxy_get_object_path (conn)); else if (tp_str_empty (sender_id)) DEBUG ("Message received on %s doesn't include message-sender-id, " "please fix CM", tp_proxy_get_object_path (self)); } out: if (out_sender_id != NULL) *out_sender_id = sender_id; return handle; } static void prepare_sender_async (TpTextChannel *self, const GPtrArray *message, gboolean fallback_to_self, GAsyncReadyCallback callback, gpointer user_data) { TpChannel *channel = (TpChannel *) self; TpContact *contact; TpHandle handle; const gchar *id; handle = get_sender (self, message, &contact, &id); if (contact == NULL && fallback_to_self) { TpConnection *conn; conn = tp_channel_get_connection ((TpChannel *) self); DEBUG ("Failed to get our self contact, please fix CM (%s)", tp_proxy_get_object_path (conn)); /* Use the connection self contact as a fallback */ contact = tp_connection_get_self_contact (conn); if (contact != NULL) g_object_ref (contact); } if (contact != NULL) { GPtrArray *contacts = g_ptr_array_new_with_free_func (g_object_unref); /* get_sender() refs the contact, we give that ref to the ptr-array */ g_ptr_array_add (contacts, contact); _tp_channel_contacts_queue_prepare_async (channel, contacts, callback, user_data); g_ptr_array_unref (contacts); } else if (id != NULL) { GPtrArray *ids = g_ptr_array_new_with_free_func (g_free); g_ptr_array_add (ids, g_strdup (id)); _tp_channel_contacts_queue_prepare_by_id_async (channel, ids, callback, user_data); g_ptr_array_unref (ids); } else if (handle != 0) { GArray *handles = g_array_new (FALSE, FALSE, sizeof (TpHandle)); g_array_append_val (handles, handle); _tp_channel_contacts_queue_prepare_by_handle_async (channel, handles, callback, user_data); g_array_unref (handles); } else { /* No sender. Still need to go through the queue to prevent reordering */ _tp_channel_contacts_queue_prepare_async (channel, NULL, callback, user_data); } } static TpContact * prepare_sender_finish (TpTextChannel *self, GAsyncResult *result, GError **error) { GPtrArray *contacts; TpContact *sender = NULL; _tp_channel_contacts_queue_prepare_finish ((TpChannel *) self, result, &contacts, error); if (contacts != NULL && contacts->len > 0) sender = g_object_ref (g_ptr_array_index (contacts, 0)); tp_clear_pointer (&contacts, g_ptr_array_unref); return sender; } static GPtrArray * copy_parts (const GPtrArray *parts) { return g_boxed_copy (TP_ARRAY_TYPE_MESSAGE_PART_LIST, parts); } static void free_parts (GPtrArray *parts) { g_boxed_free (TP_ARRAY_TYPE_MESSAGE_PART_LIST, parts); } typedef struct { GPtrArray *parts; guint flags; gchar *token; } MessageSentData; static void message_sent_sender_ready_cb (GObject *object, GAsyncResult *result, gpointer user_data) { TpTextChannel *self = (TpTextChannel *) object; MessageSentData *data = user_data; TpContact *sender; TpMessage *msg; sender = prepare_sender_finish (self, result, NULL); msg = _tp_signalled_message_new (data->parts, sender); g_signal_emit (self, signals[SIG_MESSAGE_SENT], 0, msg, data->flags, data->token); g_object_unref (msg); free_parts (data->parts); g_free (data->token); g_slice_free (MessageSentData, data); } static void message_sent_cb (TpChannel *channel, const GPtrArray *parts, guint flags, const gchar *token, gpointer user_data, GObject *weak_object) { TpTextChannel *self = (TpTextChannel *) channel; MessageSentData *data; DEBUG ("New message sent"); data = g_slice_new (MessageSentData); data->parts = copy_parts (parts); data->flags = flags; data->token = tp_str_empty (token) ? NULL : g_strdup (token); prepare_sender_async (self, parts, TRUE, message_sent_sender_ready_cb, data); } static void chat_state_changed_cb (TpTextChannel *self, TpHandle handle, TpChannelChatState state) { TpConnection *conn; TpContact *contact; /* We have only an handle, but since we guarantee "contact-chat-state-changed" * to be emitted only if TP_CHANNEL_FEATURE_GROUP and * TP_CHANNEL_FEATURE_CONTACTS has been prepared, we should already have its * TpContact. If the TpContact does not exist, telling its chat state is * useless anyway. */ conn = tp_channel_get_connection ((TpChannel *) self); contact = tp_connection_dup_contact_if_possible (conn, handle, NULL); if (contact == NULL) return; g_signal_emit (self, signals[SIG_CONTACT_CHAT_STATE_CHANGED], 0, contact, state); g_object_unref (contact); } static void tp_text_channel_prepare_chat_states_async (TpProxy *proxy, const TpProxyFeature *feature, GAsyncReadyCallback callback, gpointer user_data) { /* This feature depends on TP_CHANNEL_FEATURE_CHAT_STATES so it's already * prepared. */ tp_simple_async_report_success_in_idle ((GObject *) proxy, callback, user_data, tp_text_channel_prepare_chat_states_async); } static void tp_text_channel_constructed (GObject *obj) { TpTextChannel *self = (TpTextChannel *) obj; void (*chain_up) (GObject *) = ((GObjectClass *) tp_text_channel_parent_class)->constructed; TpChannel *chan = (TpChannel *) obj; GHashTable *props; gboolean valid; GError *err = NULL; if (chain_up != NULL) chain_up (obj); if (tp_channel_get_channel_type_id (chan) != TP_IFACE_QUARK_CHANNEL_TYPE_TEXT) { GError error = { TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT, "Channel is not of type Text" }; DEBUG ("Channel %s is not of type Text: %s", tp_proxy_get_object_path (self), tp_channel_get_channel_type (chan)); tp_proxy_invalidate (TP_PROXY (self), &error); return; } if (!tp_proxy_has_interface_by_id (self, TP_IFACE_QUARK_CHANNEL_INTERFACE_MESSAGES)) { GError error = { TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT, "Channel does not implement the Messages interface" }; DEBUG ("Channel %s does not implement the Messages interface", tp_proxy_get_object_path (self)); tp_proxy_invalidate (TP_PROXY (self), &error); return; } /* Forward TpChannel::chat-state-changed as * TpTextChannel::contact-chat-state-changed */ g_signal_connect (self, "chat-state-changed", G_CALLBACK (chat_state_changed_cb), NULL); props = _tp_channel_get_immutable_properties (TP_CHANNEL (self)); self->priv->supported_content_types = (GStrv) tp_asv_get_strv (props, TP_PROP_CHANNEL_INTERFACE_MESSAGES_SUPPORTED_CONTENT_TYPES); if (self->priv->supported_content_types == NULL) { const gchar * const plain[] = { "text/plain", NULL }; DEBUG ("Channel %s doesn't have Messages.SupportedContentTypes in its " "immutable properties", tp_proxy_get_object_path (self)); /* spec mandates that plain text is always allowed. */ self->priv->supported_content_types = g_strdupv ((GStrv) plain); } else { self->priv->supported_content_types = g_strdupv ( self->priv->supported_content_types); } self->priv->message_part_support_flags = tp_asv_get_uint32 (props, TP_PROP_CHANNEL_INTERFACE_MESSAGES_MESSAGE_PART_SUPPORT_FLAGS, &valid); if (!valid) { DEBUG ("Channel %s doesn't have Messages.MessagePartSupportFlags in its " "immutable properties", tp_proxy_get_object_path (self)); } self->priv->delivery_reporting_support = tp_asv_get_uint32 (props, TP_PROP_CHANNEL_INTERFACE_MESSAGES_DELIVERY_REPORTING_SUPPORT, &valid); if (!valid) { DEBUG ("Channel %s doesn't have Messages.DeliveryReportingSupport in its " "immutable properties", tp_proxy_get_object_path (self)); } self->priv->message_types = tp_asv_get_boxed (props, TP_PROP_CHANNEL_INTERFACE_MESSAGES_MESSAGE_TYPES, DBUS_TYPE_G_UINT_ARRAY); if (self->priv->message_types != NULL) { self->priv->message_types = g_boxed_copy (DBUS_TYPE_G_UINT_ARRAY, self->priv->message_types); } else { self->priv->message_types = g_array_new (FALSE, FALSE, sizeof (TpChannelTextMessageType)); DEBUG ("Channel %s doesn't have Messages.MessageTypes in its " "immutable properties", tp_proxy_get_object_path (self)); } tp_cli_channel_interface_messages_connect_to_message_sent (chan, message_sent_cb, NULL, NULL, NULL, &err); if (err != NULL) { WARNING ("Failed to connect to MessageSent on %s: %s", tp_proxy_get_object_path (self), err->message); g_error_free (err); } /* SMS */ self->priv->sms_flash = tp_asv_get_boolean (props, TP_PROP_CHANNEL_INTERFACE_SMS_FLASH, NULL); } static void add_message_received (TpTextChannel *self, const GPtrArray *parts, TpContact *sender, gboolean fire_received) { TpMessage *msg; msg = _tp_signalled_message_new (parts, sender); g_queue_push_tail (self->priv->pending_messages, msg); if (fire_received) g_signal_emit (self, signals[SIG_MESSAGE_RECEIVED], 0, msg); } static void message_received_sender_ready_cb (GObject *object, GAsyncResult *result, gpointer user_data) { TpTextChannel *self = (TpTextChannel *) object; GPtrArray *parts = user_data; TpContact *sender; sender = prepare_sender_finish (self, result, NULL); add_message_received (self, parts, sender, TRUE); free_parts (parts); } static void message_received_cb (TpChannel *proxy, const GPtrArray *message, gpointer user_data, GObject *weak_object) { TpTextChannel *self = user_data; /* If we are still retrieving pending messages, no need to add the message, * it will be in the initial set of messages retrieved. */ if (!self->priv->got_initial_messages) return; DEBUG ("New message received"); prepare_sender_async (self, message, FALSE, message_received_sender_ready_cb, copy_parts (message)); } static gint find_msg_by_id (gconstpointer a, gconstpointer b) { TpMessage *msg = TP_MESSAGE (a); guint id = GPOINTER_TO_UINT (b); gboolean valid; guint msg_id; msg_id = _tp_signalled_message_get_pending_message_id (msg, &valid); if (!valid) return 1; return msg_id != id; } static void pending_messages_removed_ready_cb (GObject *object, GAsyncResult *result, gpointer user_data) { TpTextChannel *self = (TpTextChannel *) object; TpChannel *channel = (TpChannel *) self; GArray *ids = user_data; guint i; _tp_channel_contacts_queue_prepare_finish (channel, result, NULL, NULL); for (i = 0; i < ids->len; i++) { guint id = g_array_index (ids, guint, i); GList *link_; TpMessage *msg; link_ = g_queue_find_custom (self->priv->pending_messages, GUINT_TO_POINTER (id), find_msg_by_id); if (link_ == NULL) { DEBUG ("Unable to find pending message having id %d", id); continue; } msg = link_->data; g_queue_delete_link (self->priv->pending_messages, link_); g_signal_emit (self, signals[SIG_PENDING_MESSAGE_REMOVED], 0, msg); g_object_unref (msg); } g_array_unref (ids); } static void pending_messages_removed_cb (TpChannel *channel, const GArray *ids, gpointer user_data, GObject *weak_object) { TpTextChannel *self = (TpTextChannel *) channel; GArray *ids_dup; if (!self->priv->got_initial_messages) return; /* We have nothing to prepare, but still we have to hook into the queue * to preserve order. Messages removed here could still be in that queue. */ ids_dup = g_array_sized_new (FALSE, FALSE, sizeof (guint), ids->len); g_array_append_vals (ids_dup, ids->data, ids->len); _tp_channel_contacts_queue_prepare_async (channel, NULL, pending_messages_removed_ready_cb, ids_dup); } static void pending_message_sender_ready_cb (GObject *object, GAsyncResult *result, gpointer user_data) { TpTextChannel *self = (TpTextChannel *) object; GPtrArray *parts = user_data; TpContact *sender; sender = prepare_sender_finish (self, result, NULL); add_message_received (self, parts, sender, FALSE); self->priv->n_preparing_pending_messages--; if (self->priv->n_preparing_pending_messages == 0) { g_simple_async_result_complete (self->priv->pending_messages_result); g_clear_object (&self->priv->pending_messages_result); } free_parts (parts); } /* There is no TP_ARRAY_TYPE_PENDING_TEXT_MESSAGE_LIST_LIST (fdo #32433) */ #define ARRAY_TYPE_PENDING_TEXT_MESSAGE_LIST_LIST dbus_g_type_get_collection (\ "GPtrArray", TP_ARRAY_TYPE_MESSAGE_PART_LIST) static void get_pending_messages_cb (TpProxy *proxy, const GValue *value, const GError *error, gpointer user_data, GObject *weak_object) { TpTextChannel *self = (TpTextChannel *) proxy; GPtrArray *messages; guint i; self->priv->got_initial_messages = TRUE; if (error != NULL) { DEBUG ("Failed to get PendingMessages property: %s", error->message); g_simple_async_result_set_error (self->priv->pending_messages_result, error->domain, error->code, "Failed to get PendingMessages property: %s", error->message); g_simple_async_result_complete_in_idle (self->priv->pending_messages_result); g_clear_object (&self->priv->pending_messages_result); return; } if (!G_VALUE_HOLDS (value, ARRAY_TYPE_PENDING_TEXT_MESSAGE_LIST_LIST)) { DEBUG ("PendingMessages property is of the wrong type"); g_simple_async_result_set_error (self->priv->pending_messages_result, TP_ERROR, TP_ERROR_CONFUSED, "PendingMessages property is of the wrong type"); g_simple_async_result_complete_in_idle (self->priv->pending_messages_result); g_clear_object (&self->priv->pending_messages_result); return; } messages = g_value_get_boxed (value); if (messages->len == 0) { g_simple_async_result_complete_in_idle (self->priv->pending_messages_result); g_clear_object (&self->priv->pending_messages_result); return; } self->priv->n_preparing_pending_messages = messages->len; for (i = 0; i < messages->len; i++) { GPtrArray *parts = g_ptr_array_index (messages, i); prepare_sender_async (self, parts, FALSE, pending_message_sender_ready_cb, copy_parts (parts)); } } static void tp_text_channel_prepare_incoming_messages_async (TpProxy *proxy, const TpProxyFeature *feature, GAsyncReadyCallback callback, gpointer user_data) { TpTextChannel *self = (TpTextChannel *) proxy; TpChannel *channel = (TpChannel *) self; GError *error = NULL; tp_cli_channel_interface_messages_connect_to_message_received (channel, message_received_cb, proxy, NULL, G_OBJECT (proxy), &error); if (error != NULL) { g_simple_async_report_take_gerror_in_idle ((GObject *) self, callback, user_data, error); return; } tp_cli_channel_interface_messages_connect_to_pending_messages_removed ( channel, pending_messages_removed_cb, proxy, NULL, G_OBJECT (proxy), &error); if (error != NULL) { g_simple_async_report_take_gerror_in_idle ((GObject *) self, callback, user_data, error); return; } g_assert (self->priv->pending_messages_result == NULL); self->priv->pending_messages_result = g_simple_async_result_new ( (GObject *) proxy, callback, user_data, tp_text_channel_prepare_incoming_messages_async); tp_cli_dbus_properties_call_get (proxy, -1, TP_IFACE_CHANNEL_INTERFACE_MESSAGES, "PendingMessages", get_pending_messages_cb, NULL, NULL, G_OBJECT (proxy)); } static void get_sms_channel_cb (TpProxy *proxy, const GValue *value, const GError *error, gpointer user_data, GObject *weak_object) { TpTextChannel *self = (TpTextChannel *) proxy; GSimpleAsyncResult *result = user_data; if (error != NULL) { DEBUG ("Failed to get SMSChannel property: %s", error->message); g_simple_async_result_set_error (result, error->domain, error->code, "Failed to get SMSChannel property: %s", error->message); goto out; } if (!G_VALUE_HOLDS (value, G_TYPE_BOOLEAN)) { DEBUG ("SMSChannel property is of the wrong type"); g_simple_async_result_set_error (result, TP_ERROR, TP_ERROR_CONFUSED, "SMSChannel property is of the wrong type"); goto out; } self->priv->is_sms_channel = g_value_get_boolean (value); /* self->priv->is_sms_channel is set to FALSE by default, so only notify the * property change is it is now set to TRUE. */ if (self->priv->is_sms_channel) g_object_notify (G_OBJECT (self), "is-sms-channel"); out: g_simple_async_result_complete_in_idle (result); g_object_unref (result); } static void sms_channel_changed_cb (TpChannel *proxy, gboolean sms, gpointer user_data, GObject *weak_object) { TpTextChannel *self = (TpTextChannel *) proxy; if (self->priv->is_sms_channel == sms) return; self->priv->is_sms_channel = sms; g_object_notify (weak_object, "is-sms-channel"); } static void tp_text_channel_prepare_sms_async (TpProxy *proxy, const TpProxyFeature *feature, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; GError *error = NULL; result = g_simple_async_result_new ((GObject *) proxy, callback, user_data, tp_text_channel_prepare_sms_async); tp_cli_channel_interface_sms_connect_to_sms_channel_changed ( (TpChannel *) proxy, sms_channel_changed_cb, NULL, NULL, G_OBJECT (proxy), &error); if (error != NULL) { WARNING ("Failed to connect to SMS.SMSChannelChanged: %s", error->message); g_error_free (error); } tp_cli_dbus_properties_call_get (proxy, -1, TP_IFACE_CHANNEL_INTERFACE_SMS, "SMSChannel", get_sms_channel_cb, result, NULL, G_OBJECT (proxy)); } enum { FEAT_INCOMING_MESSAGES, FEAT_SMS, FEAT_CHAT_STATES, N_FEAT }; static const TpProxyFeature * tp_text_channel_list_features (TpProxyClass *cls G_GNUC_UNUSED) { static TpProxyFeature features[N_FEAT + 1] = { { 0 } }; static GQuark need_sms[2] = {0, 0}; static GQuark depends_chat_state[2] = {0, 0}; if (G_LIKELY (features[0].name != 0)) return features; features[FEAT_INCOMING_MESSAGES].name = TP_TEXT_CHANNEL_FEATURE_INCOMING_MESSAGES; features[FEAT_INCOMING_MESSAGES].prepare_async = tp_text_channel_prepare_incoming_messages_async; features[FEAT_SMS].name = TP_TEXT_CHANNEL_FEATURE_SMS; features[FEAT_SMS].prepare_async = tp_text_channel_prepare_sms_async; need_sms[0] = TP_IFACE_QUARK_CHANNEL_INTERFACE_SMS; features[FEAT_SMS].interfaces_needed = need_sms; G_GNUC_BEGIN_IGNORE_DEPRECATIONS features[FEAT_CHAT_STATES].name = TP_TEXT_CHANNEL_FEATURE_CHAT_STATES; features[FEAT_CHAT_STATES].prepare_async = tp_text_channel_prepare_chat_states_async; depends_chat_state[0] = TP_CHANNEL_FEATURE_CHAT_STATES; features[FEAT_CHAT_STATES].depends_on = depends_chat_state; G_GNUC_END_IGNORE_DEPRECATIONS /* assert that the terminator at the end is there */ g_assert (features[N_FEAT].name == 0); return features; } static void tp_text_channel_class_init (TpTextChannelClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); TpProxyClass *proxy_class = (TpProxyClass *) klass; GParamSpec *param_spec; gobject_class->constructed = tp_text_channel_constructed; gobject_class->get_property = tp_text_channel_get_property; gobject_class->dispose = tp_text_channel_dispose; proxy_class->list_features = tp_text_channel_list_features; /** * TpTextChannel:supported-content-types: * * A #GStrv containing the MIME types supported by this channel, with more * preferred MIME types appearing earlier in the array. * * Since: 0.13.10 */ param_spec = g_param_spec_boxed ("supported-content-types", "SupportedContentTypes", "The Messages.SupportedContentTypes property of the channel", G_TYPE_STRV, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_SUPPORTED_CONTENT_TYPES, param_spec); /** * TpTextChannel:message-part-support-flags: * * A #TpMessagePartSupportFlags indicating the level of support for * message parts on this channel. * * Since: 0.13.10 */ param_spec = g_param_spec_uint ("message-part-support-flags", "MessagePartSupportFlags", "The Messages.MessagePartSupportFlags property of the channel", 0, G_MAXUINT32, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_MESSAGE_PART_SUPPORT_FLAGS, param_spec); /** * TpTextChannel:delivery-reporting-support: * * A #TpDeliveryReportingSupportFlags indicating features supported * by this channel. * * Since: 0.13.10 */ param_spec = g_param_spec_uint ("delivery-reporting-support", "DeliveryReportingSupport", "The Messages.DeliveryReportingSupport property of the channel", 0, G_MAXUINT32, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_DELIVERY_REPORTING_SUPPORT, param_spec); /** * TpTextChannel:message-types: * * A #GArray containing the #TpChannelTextMessageType which may be sent on * this channel. * * Since: 0.13.16 */ param_spec = g_param_spec_boxed ("message-types", "MessageTypes", "The Messages.MessageTypes property of the channel", DBUS_TYPE_G_UINT_ARRAY, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_MESSAGE_TYPES, param_spec); /** * TpTextChannel:is-sms-channel: * * %TRUE if messages sent and received on this channel are transmitted * via SMS. * * This property is not guaranteed to have a meaningful value until * TP_TEXT_CHANNEL_FEATURE_SMS has been prepared. * * Since: 0.15.1 */ param_spec = g_param_spec_boolean ("is-sms-channel", "is SMS channel", "The SMS.SMSChannel property of the channel", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_IS_SMS_CHANNEL, param_spec); /** * TpTextChannel:sms-flash: * * %TRUE if this channel is exclusively for receiving class 0 SMSes * (and no SMSes can be sent using tp_text_channel_send_message_async() * on this channel). If %FALSE, no incoming class 0 SMSes will appear * on this channel. * * Since: 0.15.1 */ param_spec = g_param_spec_boolean ("sms-flash", "SMS flash", "The SMS.Flash property of the channel", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_SMS_FLASH, param_spec); /** * TpTextChannel::message-received: * @self: the #TpTextChannel * @message: a #TpSignalledMessage * * The ::message-received signal is emitted when a new message has been * received on @self. * * The same @message object will be used by the * #TpTextChannel::pending-message-removed signal once @message has been * acked so you can simply compare pointers to identify the message. * * Note that this signal is only fired once the * #TP_TEXT_CHANNEL_FEATURE_INCOMING_MESSAGES has been prepared. * * It is guaranteed that @message's #TpSignalledMessage:sender has all of the * features previously passed to * tp_simple_client_factory_add_contact_features() prepared. * * Since: 0.13.10 */ signals[SIG_MESSAGE_RECEIVED] = g_signal_new ("message-received", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, TP_TYPE_SIGNALLED_MESSAGE); /** * TpTextChannel::pending-message-removed: * @self: the #TpTextChannel * @message: a #TpSignalledMessage * * The ::pending-message-removed signal is emitted when @message * has been acked and so removed from the pending messages list. * * Note that this signal is only fired once the * #TP_TEXT_CHANNEL_FEATURE_INCOMING_MESSAGES has been prepared. * * It is guaranteed that @message's #TpSignalledMessage:sender has all of the * features previously passed to * tp_simple_client_factory_add_contact_features() prepared. * * Since: 0.13.10 */ signals[SIG_PENDING_MESSAGE_REMOVED] = g_signal_new ( "pending-message-removed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, TP_TYPE_SIGNALLED_MESSAGE); /** * TpTextChannel::message-sent: * @self: the #TpTextChannel * @message: a #TpSignalledMessage * @flags: the #TpMessageSendingFlags affecting how the message was sent * @token: an opaque token used to match any incoming delivery or failure * reports against this message, or %NULL if the message is not * readily identifiable. * * The ::message-sent signal is emitted when @message * has been submitted for sending. * * It is guaranteed that @message's #TpSignalledMessage:sender has all of the * features previously passed to * tp_simple_client_factory_add_contact_features() prepared. * * Since: 0.13.10 */ signals[SIG_MESSAGE_SENT] = g_signal_new ( "message-sent", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 3, TP_TYPE_SIGNALLED_MESSAGE, G_TYPE_UINT, G_TYPE_STRING); g_type_class_add_private (gobject_class, sizeof (TpTextChannelPrivate)); /** * TpTextChannel::contact-chat-state-changed: * @self: a channel * @contact: a #TpContact for the local user or another contact * @state: the new #TpChannelChatState for the contact * * Emitted when a contact's chat state changes after tp_proxy_prepare_async() * has finished preparing features %TP_TEXT_CHANNEL_FEATURE_CHAT_STATES, * %TP_CHANNEL_FEATURE_GROUP and %TP_CHANNEL_FEATURE_CONTACTS. * * Since: 0.19.0 */ signals[SIG_CONTACT_CHAT_STATE_CHANGED] = g_signal_new ( "contact-chat-state-changed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, TP_TYPE_CONTACT, G_TYPE_UINT); } static void tp_text_channel_init (TpTextChannel *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), TP_TYPE_TEXT_CHANNEL, TpTextChannelPrivate); self->priv->pending_messages = g_queue_new (); } /** * tp_text_channel_new: * @conn: a #TpConnection; may not be %NULL * @object_path: the object path of the channel; may not be %NULL * @immutable_properties: (transfer none) (element-type utf8 GObject.Value): * the immutable properties of the channel, * as signalled by the NewChannel D-Bus signal or returned by the * CreateChannel and EnsureChannel D-Bus methods: a mapping from * strings (D-Bus interface name + "." + property name) to #GValue instances * @error: used to indicate the error if %NULL is returned * * Convenient function to create a new #TpTextChannel * * Returns: (transfer full): a newly created #TpTextChannel * * Since: 0.13.10 * Deprecated: Use tp_simple_client_factory_ensure_channel() instead. */ TpTextChannel * tp_text_channel_new (TpConnection *conn, const gchar *object_path, const GHashTable *immutable_properties, GError **error) { return _tp_text_channel_new_with_factory (NULL, conn, object_path, immutable_properties, error); } TpTextChannel * _tp_text_channel_new_with_factory ( TpSimpleClientFactory *factory, TpConnection *conn, const gchar *object_path, const GHashTable *immutable_properties, GError **error) { TpProxy *conn_proxy = (TpProxy *) conn; g_return_val_if_fail (TP_IS_CONNECTION (conn), NULL); g_return_val_if_fail (object_path != NULL, NULL); g_return_val_if_fail (immutable_properties != NULL, NULL); if (!tp_dbus_check_valid_object_path (object_path, error)) return NULL; return g_object_new (TP_TYPE_TEXT_CHANNEL, "connection", conn, "dbus-daemon", conn_proxy->dbus_daemon, "bus-name", conn_proxy->bus_name, "object-path", object_path, "handle-type", (guint) TP_UNKNOWN_HANDLE_TYPE, "channel-properties", immutable_properties, "factory", factory, NULL); } /** * tp_text_channel_get_supported_content_types: * @self: a #TpTextChannel * * Return the #TpTextChannel:supported-content-types property * * Returns: (transfer none) : * the value of #TpTextChannel:supported-content-types * * Since: 0.13.10 */ const gchar * const * tp_text_channel_get_supported_content_types (TpTextChannel *self) { g_return_val_if_fail (TP_IS_TEXT_CHANNEL (self), NULL); return (const gchar * const *) self->priv->supported_content_types; } /** * tp_text_channel_get_message_part_support_flags: * @self: a #TpTextChannel * * Return the #TpTextChannel:message-part-support-flags property * * Returns: the value of #TpTextChannel:message-part-support-flags * * Since: 0.13.10 */ TpMessagePartSupportFlags tp_text_channel_get_message_part_support_flags ( TpTextChannel *self) { g_return_val_if_fail (TP_IS_TEXT_CHANNEL (self), 0); return self->priv->message_part_support_flags; } /** * tp_text_channel_get_delivery_reporting_support: * @self: a #TpTextChannel * * Return the #TpTextChannel:delivery-reporting-support property * * Returns: the value of #TpTextChannel:delivery-reporting-support property * * Since: 0.13.10 */ TpDeliveryReportingSupportFlags tp_text_channel_get_delivery_reporting_support ( TpTextChannel *self) { g_return_val_if_fail (TP_IS_TEXT_CHANNEL (self), 0); return self->priv->delivery_reporting_support; } /** * TP_TEXT_CHANNEL_FEATURE_INCOMING_MESSAGES: * * Expands to a call to a function that returns a quark representing the * incoming messages features of a #TpTextChannel. * * When this feature is prepared, tp_text_channel_dup_pending_messages() will * return a non-empty list if any unacknowledged messages are waiting, and the * #TpTextChannel::message-received and #TpTextChannel::pending-message-removed * signals will be emitted. * * One can ask for a feature to be prepared using the * tp_proxy_prepare_async() function, and waiting for it to callback. * * Since: 0.13.10 */ GQuark tp_text_channel_get_feature_quark_incoming_messages (void) { return g_quark_from_static_string ( "tp-text-channel-feature-incoming-messages"); } /** * tp_text_channel_get_pending_messages: * @self: a #TpTextChannel * * Return a newly allocated list of unacknowledged #TpSignalledMessage * objects. * * It is guaranteed that the #TpSignalledMessage:sender of each * #TpSignalledMessage has all of the features previously passed to * tp_simple_client_factory_add_contact_features() prepared. * * Returns: (transfer container) (element-type TelepathyGLib.SignalledMessage): * a #GList of borrowed #TpSignalledMessage * * Since: 0.13.10 * Deprecated: Since 0.19.9. New code should use * tp_text_channel_dup_pending_messages() instead. */ GList * tp_text_channel_get_pending_messages (TpTextChannel *self) { return g_list_copy (g_queue_peek_head_link (self->priv->pending_messages)); } /** * tp_text_channel_dup_pending_messages: * @self: a #TpTextChannel * * Return a newly allocated list of unacknowledged #TpSignalledMessage * objects. * * It is guaranteed that the #TpSignalledMessage:sender of each * #TpSignalledMessage has all of the features previously passed to * tp_simple_client_factory_add_contact_features() prepared. * * Returns: (transfer full) (element-type TelepathyGLib.SignalledMessage): * a #GList of reffed #TpSignalledMessage * * Since: 0.19.9 */ GList * tp_text_channel_dup_pending_messages (TpTextChannel *self) { return _tp_g_list_copy_deep ( g_queue_peek_head_link (self->priv->pending_messages), (GCopyFunc) g_object_ref, NULL); } static void send_message_cb (TpChannel *proxy, const gchar *token, const GError *error, gpointer user_data, GObject *weak_object) { GSimpleAsyncResult *result = user_data; if (error != NULL) { DEBUG ("Failed to send message: %s", error->message); g_simple_async_result_set_from_error (result, error); } g_simple_async_result_set_op_res_gpointer (result, tp_str_empty (token) ? NULL : g_strdup (token), g_free); g_simple_async_result_complete_in_idle (result); g_object_unref (result); } /** * tp_text_channel_send_message_async: * @self: a #TpTextChannel * @message: a #TpClientMessage * @flags: flags affecting how the message is sent * @callback: a callback to call when the message has been submitted to the * server * @user_data: data to pass to @callback * * Submit a message to the server for sending. Once the message has been * submitted to the sever, @callback will be called. You can then call * tp_text_channel_send_message_finish() to get the result of the operation. * * Since: 0.13.10 */ void tp_text_channel_send_message_async (TpTextChannel *self, TpMessage *message, TpMessageSendingFlags flags, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; g_return_if_fail (TP_IS_TEXT_CHANNEL (self)); g_return_if_fail (TP_IS_CLIENT_MESSAGE (message)); result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, tp_text_channel_send_message_async); tp_cli_channel_interface_messages_call_send_message (TP_CHANNEL (self), -1, message->parts, flags, send_message_cb, result, NULL, NULL); } /** * tp_text_channel_send_message_finish: * @self: a #TpTextChannel * @result: a #GAsyncResult passed to the callback for tp_text_channel_send_message_async() * @token: (out) (transfer full): if not %NULL, used to return the * token of the sent message * @error: a #GError to fill * * Completes a call to tp_text_channel_send_message_async(). * * @token can be used to match any incoming delivery or failure reports * against the sent message. If this function returns true but the returned * token is %NULL, the message was sent successfully but the protocol does not * provide a way to identify it later. * * Returns: %TRUE if the message has been submitted to the server, %FALSE * otherwise. * * Since: 0.13.10 */ gboolean tp_text_channel_send_message_finish (TpTextChannel *self, GAsyncResult *result, gchar **token, GError **error) { _tp_implement_finish_copy_pointer (self, tp_text_channel_send_message_async, g_strdup, token); } static void acknowledge_pending_messages_ready_cb (GObject *object, GAsyncResult *res, gpointer user_data) { TpChannel *channel = (TpChannel *) object; GSimpleAsyncResult *result = user_data; _tp_channel_contacts_queue_prepare_finish (channel, res, NULL, NULL); g_simple_async_result_complete_in_idle (result); g_object_unref (result); } static void acknowledge_pending_messages_cb (TpChannel *channel, const GError *error, gpointer user_data, GObject *weak_object) { GSimpleAsyncResult *result = user_data; if (error != NULL) { DEBUG ("Failed to ack messages: %s", error->message); g_simple_async_result_set_from_error (result, error); g_simple_async_result_complete_in_idle (result); g_object_unref (result); return; } /* We should have already got MessagesRemoved signal, but it could still be * in the queue. So we have to hook into that queue before complete to be * sure messages are removed from pending_messages */ _tp_channel_contacts_queue_prepare_async (channel, NULL, acknowledge_pending_messages_ready_cb, result); } /** * tp_text_channel_ack_messages_async: * @self: a #TpTextChannel * @messages: (element-type TelepathyGLib.SignalledMessage): a #GList of * #TpSignalledMessage * @callback: a callback to call when the message have been acked * @user_data: data to pass to @callback * * Acknowledge all the messages in @messages. * Once the messages have been acked, @callback will be called. * You can then call tp_text_channel_ack_messages_finish() to get the * result of the operation. * * You should use the #TpSignalledMessage received from * tp_text_channel_dup_pending_messages() or the * #TpTextChannel::message-received signal. * * See tp_text_channel_ack_message_async() about acknowledging messages. * * Since: 0.13.10 */ void tp_text_channel_ack_messages_async (TpTextChannel *self, const GList *messages, GAsyncReadyCallback callback, gpointer user_data) { TpChannel *chan = (TpChannel *) self; GArray *ids; GList *l; GSimpleAsyncResult *result; g_return_if_fail (TP_IS_TEXT_CHANNEL (self)); result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, tp_text_channel_ack_messages_async); if (messages == NULL) { /* Nothing to ack, succeed immediately */ g_simple_async_result_complete_in_idle (result); g_object_unref (result); return; } ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), g_list_length ((GList *) messages)); for (l = (GList *) messages; l != NULL; l = g_list_next (l)) { TpMessage *msg = l->data; guint id; gboolean valid; g_return_if_fail (TP_IS_SIGNALLED_MESSAGE (msg)); id = _tp_signalled_message_get_pending_message_id (msg, &valid); if (!valid) { DEBUG ("Message doesn't have pending-message-id ?!"); continue; } g_array_append_val (ids, id); } tp_cli_channel_type_text_call_acknowledge_pending_messages (chan, -1, ids, acknowledge_pending_messages_cb, result, NULL, G_OBJECT (self)); g_array_unref (ids); } /** * tp_text_channel_ack_messages_finish: * @self: a #TpTextChannel * @result: a #GAsyncResult passed to the callback for tp_text_channel_ack_messages_async() * @error: a #GError to fill * * Finishes acknowledging a list of messages. * * Returns: %TRUE if the messages have been acked, %FALSE otherwise. * * Since: 0.13.10 */ gboolean tp_text_channel_ack_messages_finish (TpTextChannel *self, GAsyncResult *result, GError **error) { _tp_implement_finish_void (self, tp_text_channel_ack_messages_async) } /** * tp_text_channel_ack_message_async: * @self: a #TpTextChannel * @message: a #TpSignalledMessage * @callback: a callback to call when the message have been acked * @user_data: data to pass to @callback * * Acknowledge @message. Once the message has been acked, @callback will be * called. You can then call tp_text_channel_ack_message_finish() to get the * result of the operation. * * A message should be acknowledged once it has been shown to the user by the * Handler of the channel. So Observers and Approvers should NOT acknowledge * messages themselves. * Once a message has been acknowledged, it is removed from the * pending-message queue and so the #TpTextChannel::pending-message-removed * signal is fired. * * You should use the #TpSignalledMessage received from * tp_text_channel_dup_pending_messages() or the * #TpTextChannel::message-received signal. * * Since: 0.13.10 */ void tp_text_channel_ack_message_async (TpTextChannel *self, TpMessage *message, GAsyncReadyCallback callback, gpointer user_data) { TpChannel *chan = (TpChannel *) self; GSimpleAsyncResult *result; GArray *ids; guint id; gboolean valid; g_return_if_fail (TP_IS_TEXT_CHANNEL (self)); g_return_if_fail (TP_IS_SIGNALLED_MESSAGE (message)); id = _tp_signalled_message_get_pending_message_id (message, &valid); if (!valid) { g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "Message doesn't have a pending-message-id"); return; } result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, tp_text_channel_ack_message_async); ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1); g_array_append_val (ids, id); tp_cli_channel_type_text_call_acknowledge_pending_messages (chan, -1, ids, acknowledge_pending_messages_cb, result, NULL, G_OBJECT (self)); g_array_unref (ids); } /** * tp_text_channel_ack_message_finish: * @self: a #TpTextChannel * @result: a #GAsyncResult passed to the callback for tp_text_channel_ack_message_async() * @error: a #GError to fill * * Finishes acknowledging a message. * * Returns: %TRUE if the message has been acked, %FALSE otherwise. * * Since: 0.13.10 */ gboolean tp_text_channel_ack_message_finish (TpTextChannel *self, GAsyncResult *result, GError **error) { _tp_implement_finish_void (self, tp_text_channel_ack_message_async) } /** * TP_TEXT_CHANNEL_FEATURE_CHAT_STATES: * * Expands to a call to a function that returns a quark representing the * chat states feature on a #TpTextChannel. * * When this feature is prepared, tp_text_channel_get_chat_state() and the * #TpTextChannel::contact-chat-state-changed signal become useful. * * One can ask for a feature to be prepared using the * tp_proxy_prepare_async() function, and waiting for it to callback. * * Since: 0.19.0 */ GQuark tp_text_channel_get_feature_quark_chat_states (void) { return g_quark_from_static_string ("tp-text-channel-feature-chat-states"); } /** * tp_text_channel_get_chat_state: * @self: a channel * @contact: a #TpContact * * Return the chat state for the given contact. If tp_proxy_is_prepared() * would return %FALSE for the feature %TP_TEXT_CHANNEL_FEATURE_CHAT_STATES, * the result will always be %TP_CHANNEL_CHAT_STATE_INACTIVE. * * Returns: the chat state for @contact, or %TP_CHANNEL_CHAT_STATE_INACTIVE * if their chat state is not known * Since: 0.19.0 */ TpChannelChatState tp_text_channel_get_chat_state (TpTextChannel *self, TpContact *contact) { g_return_val_if_fail (TP_IS_TEXT_CHANNEL (self), 0); /* Use the deprecated function internally to avoid duplicated introspection */ G_GNUC_BEGIN_IGNORE_DEPRECATIONS return tp_channel_get_chat_state ((TpChannel *) self, tp_contact_get_handle (contact)); G_GNUC_END_IGNORE_DEPRECATIONS } static void set_chat_state_cb (TpChannel *proxy, const GError *error, gpointer user_data, GObject *weak_object) { GSimpleAsyncResult *result = user_data; if (error != NULL) { DEBUG ("SetChatState failed: %s", error->message); g_simple_async_result_set_from_error (result, error); } g_simple_async_result_complete_in_idle (result); g_object_unref (result); } /** * tp_text_channel_set_chat_state_async: * @self: a #TpTextChannel * @state: a #TpChannelChatState to set * @callback: a callback to call when the chat state has been set * @user_data: data to pass to @callback * * Set the local state on channel @self to @state. * Once the state has been set, @callback will be called. * You can then call tp_text_channel_set_chat_state_finish() to get the * result of the operation. * * Since: 0.13.10 */ void tp_text_channel_set_chat_state_async (TpTextChannel *self, TpChannelChatState state, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, tp_text_channel_set_chat_state_async); tp_cli_channel_interface_chat_state_call_set_chat_state (TP_CHANNEL (self), -1, state, set_chat_state_cb, result, NULL, G_OBJECT (self)); } /** * tp_text_channel_set_chat_state_finish: * @self: a #TpTextChannel * @result: a #GAsyncResult passed to the callback for tp_text_channel_set_chat_state_async() * @error: a #GError to fill * * Completes a call to tp_text_channel_set_chat_state_async(). * * Returns: %TRUE if the chat state has been changed, %FALSE otherwise. * * Since: 0.13.10 */ gboolean tp_text_channel_set_chat_state_finish (TpTextChannel *self, GAsyncResult *result, GError **error) { _tp_implement_finish_void (self, tp_text_channel_set_chat_state_async) } /** * tp_text_channel_get_message_types: * @self: a #TpTextChannel * * Return the #TpTextChannel:message-types property * * Returns: (transfer none) (element-type TelepathyGLib.ChannelTextMessageType): * the value of #TpTextChannel:message-types * * Since: 0.13.16 */ GArray * tp_text_channel_get_message_types (TpTextChannel *self) { g_return_val_if_fail (TP_IS_TEXT_CHANNEL (self), NULL); return self->priv->message_types; } /** * tp_text_channel_supports_message_type: * @self: a #TpTextChannel * @message_type: a #TpChannelTextMessageType * * Check if message of type @message_type can be sent on this channel. * * Returns: %TRUE if message of type @message_type can be sent on @self, %FALSE * otherwise * * Since: 0.13.16 */ gboolean tp_text_channel_supports_message_type (TpTextChannel *self, TpChannelTextMessageType message_type) { guint i; for (i = 0; i < self->priv->message_types->len; i++) { TpChannelTextMessageType tmp = g_array_index (self->priv->message_types, TpChannelTextMessageType, i); if (tmp == message_type) return TRUE; } return FALSE; } /** * TP_TEXT_CHANNEL_FEATURE_SMS: * * Expands to a call to a function that returns a quark representing the * SMS feature of a #TpTextChannel. * * When this feature is prepared, the TpTextChannel:is-sms-channel property * will have a meaningful value and will be updated when needed. * * One can ask for a feature to be prepared using the * tp_proxy_prepare_async() function, and waiting for it to callback. * * Since: 0.15.1 */ GQuark tp_text_channel_get_feature_quark_sms (void) { return g_quark_from_static_string ("tp-text-channel-feature-sms"); } /** * tp_text_channel_is_sms_channel: * @self: a #TpTextChannel * * Return the #TpTextChannel:is-sms-channel property * * Returns: the value of #TpTextChannel:is-sms-channel property * * Since: 0.15.1 */ gboolean tp_text_channel_is_sms_channel (TpTextChannel *self) { g_return_val_if_fail (TP_IS_TEXT_CHANNEL (self), FALSE); return self->priv->is_sms_channel; } /** * tp_text_channel_get_sms_flash: * @self: a #TpTextChannel * * Return the #TpTextChannel:sms-flash property * * Returns: the value of #TpTextChannel:sms-flash property * * Since: 0.15.1 */ gboolean tp_text_channel_get_sms_flash (TpTextChannel *self) { g_return_val_if_fail (TP_IS_TEXT_CHANNEL (self), FALSE); return self->priv->sms_flash; } typedef struct { guint chunks_required; gint remaining_characters; gint estimated_cost; } GetSmsLengthReturn; static GetSmsLengthReturn * get_sms_length_return_new (guint chunks_required, gint remaining_characters, gint estimated_cost) { GetSmsLengthReturn *result = g_slice_new (GetSmsLengthReturn); result->chunks_required = chunks_required; result->remaining_characters = remaining_characters; result->estimated_cost = estimated_cost; return result; } static void get_sms_length_return_free (GetSmsLengthReturn *r) { g_slice_free (GetSmsLengthReturn, r); } static void get_sms_length_cb (TpChannel *proxy, guint chunks_required, gint remaining_characters, gint estimated_cost, const GError *error, gpointer user_data, GObject *weak_object) { GSimpleAsyncResult *result = user_data; GetSmsLengthReturn *r; if (error != NULL) { DEBUG ("Failed to get SMS length: %s", error->message); g_simple_async_result_set_from_error (result, error); goto out; } r = get_sms_length_return_new (chunks_required, remaining_characters, estimated_cost); g_simple_async_result_set_op_res_gpointer (result, r, (GDestroyNotify) get_sms_length_return_free); out: g_simple_async_result_complete_in_idle (result); } /** * tp_text_channel_get_sms_length_async: * @self: a #TpTextChannel * @message: a #TpClientMessage * @callback: a callback to call when the request has been satisfied * @user_data: data to pass to @callback * * Starts an async call to get the number of 140 octet chunks required to * send a #message via SMS on #self, as well as the number of remaining * characters available in the final chunk and, if possible, * an estimate of the cost. * * Once the request has been satisfied, @callback will be called. * You can then call tp_text_channel_get_sms_length_finish() to get the * result of the operation. * * Since: 0.15.1 */ void tp_text_channel_get_sms_length_async (TpTextChannel *self, TpMessage *message, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; result = g_simple_async_result_new ((GObject *) self, callback, user_data, tp_text_channel_get_sms_length_async); tp_cli_channel_interface_sms_call_get_sms_length ((TpChannel *) self, -1, message->parts, get_sms_length_cb, result, g_object_unref, G_OBJECT (self)); } /** * tp_text_channel_get_sms_length_finish: * @self: a #TpTextChannel * @result: a #GAsyncResult * @chunks_required: (out): if not %NULL used to return * the number of 140 octet chunks required to send the message. * @remaining_characters: (out): if not %NULL used to return * the number of further characters that can be fit in the final chunk. * A negative value indicates that the message will be truncated by * abs(@remaining_characters). * The value #G_MININT32 indicates the message will be truncated by * an unknown amount. * @estimated_cost: (out): if not %NULL used to return * the estimated cost of sending this message. * The currency and scale of this value are the same as the * values of the #TpConnection:balance-scale and * #TpConnection:balance-currency properties. * A value of -1 indicates the cost could not be estimated. * @error: a #GError to fill * * Finishes an async SMS length request. * * Returns: %TRUE if the number of 140 octet chunks required to send * the message has been retrieved, %FALSE otherwise. * * Since: 0.15.1 */ gboolean tp_text_channel_get_sms_length_finish (TpTextChannel *self, GAsyncResult *result, guint *chunks_required, gint *remaining_characters, gint *estimated_cost, GError **error) { GSimpleAsyncResult *simple = (GSimpleAsyncResult *) result; GetSmsLengthReturn *r; if (g_simple_async_result_propagate_error (simple, error)) return FALSE; g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self), tp_text_channel_get_sms_length_async), FALSE); r = g_simple_async_result_get_op_res_gpointer (simple); if (chunks_required != NULL) *chunks_required = r->chunks_required; if (remaining_characters != NULL) *remaining_characters = r->remaining_characters; if (estimated_cost != NULL) *estimated_cost = r->estimated_cost; return TRUE; } /** * tp_text_channel_ack_all_pending_messages_async: * @self: a #TpTextChannel * @callback: a callback to call when the messages have been acked * @user_data: data to pass to @callback * * Acknowledge all the pending messages. This is equivalent of calling * tp_text_channel_ack_messages_async() with the list of #TpSignalledMessage * returned by tp_text_channel_dup_pending_messages(). * * Once the messages have been acked, @callback will be called. * You can then call tp_text_channel_ack_all_pending_messages_finish() to get * the result of the operation. * * See tp_text_channel_ack_message_async() about acknowledging messages. * * Since: 0.15.3 */ void tp_text_channel_ack_all_pending_messages_async (TpTextChannel *self, GAsyncReadyCallback callback, gpointer user_data) { GList *messages; messages = g_queue_peek_head_link (self->priv->pending_messages); tp_text_channel_ack_messages_async (self, messages, callback, user_data); } /** * tp_text_channel_ack_all_pending_messages_finish: * @self: a #TpTextChannel * @result: a #GAsyncResult * @error: a #GError to fill * * Finish an asynchronous acknowledgement operation of all messages. * * Returns: %TRUE if the messages have been acked, %FALSE otherwise. * * Since: 0.15.3 */ gboolean tp_text_channel_ack_all_pending_messages_finish (TpTextChannel *self, GAsyncResult *result, GError **error) { return tp_text_channel_ack_messages_finish (self, result, error); }