/* * ring-text-channel.c - Source for RingTextChannel * * Copyright (C) 2007-2010 Nokia Corporation * @author Pekka Pessi * @author Lassi Syrjala * * This work 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 work 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 work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #define DEBUG_FLAG RING_DEBUG_SMS #include "ring-debug.h" #include "ring-text-channel.h" #include "ring-text-manager.h" #include "ring-connection.h" #include "ring-param-spec.h" #include "ring-util.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void channel_iface_init(gpointer iface, gpointer data); static void ring_text_channel_destroyable_iface_init(gpointer, gpointer); G_DEFINE_TYPE_WITH_CODE( RingTextChannel, ring_text_channel, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_DBUS_PROPERTIES, tp_dbus_properties_mixin_iface_init); G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CHANNEL, channel_iface_init); G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CHANNEL_TYPE_TEXT, tp_message_mixin_text_iface_init); G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CHANNEL_INTERFACE_DESTROYABLE, ring_text_channel_destroyable_iface_init); G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CHANNEL_INTERFACE_MESSAGES, tp_message_mixin_messages_iface_init); #if nomore G_IMPLEMENT_INTERFACE(RTCOM_TYPE_TP_SVC_CHANNEL_INTERFACE_SMS, NULL); #endif G_IMPLEMENT_INTERFACE(TP_TYPE_EXPORTABLE_CHANNEL, NULL); G_IMPLEMENT_INTERFACE(TP_TYPE_CHANNEL_IFACE, NULL)); static const char * const ring_text_channel_interfaces[] = { TP_IFACE_CHANNEL_INTERFACE_DESTROYABLE, TP_IFACE_CHANNEL_INTERFACE_MESSAGES, #if nomore RTCOM_TP_IFACE_CHANNEL_INTERFACE_SMS, #endif NULL }; /* type definition stuff */ enum { PROP_NONE, PROP_OBJECT_PATH, PROP_CHANNEL_PROPERTIES, PROP_CHANNEL_DESTROYED, PROP_CHANNEL_TYPE, PROP_INTERFACES, PROP_HANDLE_TYPE, PROP_HANDLE, PROP_TARGET_ID, PROP_REQUESTED, PROP_INITIATOR, PROP_INITIATOR_ID, PROP_SMS_FLASH, PROP_TARGET_MATCH, PROP_SMS_SERVICE, PROP_CONNECTION, N_PROPS }; struct _RingTextChannelPrivate { RingConnection *connection; /* property */ ModemSMSService *sms_service; /* property */ char *object_path; /* property */ TpHandle handle; /* property */ char const *target_id; /* property */ GQueue sending[1]; TpHandle initiator; /* property */ unsigned requested:1; /* property */ unsigned destroyed:1; /* property */ unsigned sms_flash:1; /* c.n.T.Channel.Interface.SMS.Flash */ unsigned disposed:1; unsigned :0; char *destination; }; /* ---------------------------------------------------------------------- */ static void ring_text_channel_close(RingTextChannel *self); static void ring_text_channel_destroy(RingTextChannel *self); static void ring_text_channel_set_target_match(GValue *, char const *, int); static void ring_text_channel_set_receive_timestamps(RingTextChannel *self, TpMessage *msg, gpointer sms); /* Sending */ static void modem_sms_request_send_reply(ModemSMSService *, ModemRequest *request, char const *token, GError const *error, gpointer _self); static void ring_text_channel_send(GObject *_self, TpMessage *message, TpMessageSendingFlags flags); /* ---------------------------------------------------------------------- */ /* Supported D-Bus properties */ /* Properties for org.freedesktop.Telepathy.Channel */ static TpDBusPropertiesMixinPropImpl channel_properties[] = { { "TargetHandle", "handle", NULL }, { "TargetID", "handle-id", NULL }, { "TargetHandleType", "handle-type", NULL }, { "ChannelType", "channel-type", NULL }, { "Interfaces", "interfaces", NULL }, { "InitiatorHandle", "initiator", NULL }, { "InitiatorID", "initiator-id", NULL }, { "Requested", "requested", NULL }, { NULL } }; #if nomore /* Properties for c.n.T.Channel.Interface.SMS. */ static TpDBusPropertiesMixinPropImpl sms_properties[] = { { "Flash", "sms-flash", NULL }, { "TargetMatch", "target-match", NULL }, { NULL } }; #endif /** Return a hash describing channel properties * * A channel's properties are constant for its lifetime on the bus, so * this property should only change when the closed signal is emitted (so * that respawned channels can reappear on the bus with different * properties). */ GHashTable * ring_text_channel_properties(RingTextChannel *self) { return tp_dbus_properties_mixin_make_properties_hash ( G_OBJECT(self), TP_IFACE_CHANNEL, "ChannelType", TP_IFACE_CHANNEL, "Interfaces", TP_IFACE_CHANNEL, "TargetHandle", TP_IFACE_CHANNEL, "TargetHandleType", TP_IFACE_CHANNEL, "TargetID", TP_IFACE_CHANNEL, "InitiatorHandle", TP_IFACE_CHANNEL, "InitiatorID", TP_IFACE_CHANNEL, "Requested", #if nomore RTCOM_TP_IFACE_CHANNEL_INTERFACE_SMS, "TargetMatch", RTCOM_TP_IFACE_CHANNEL_INTERFACE_SMS, "Flash", #endif NULL); } static TpDBusPropertiesMixinIfaceImpl ring_text_channel_dbus_property_interfaces[] = { { TP_IFACE_CHANNEL, tp_dbus_properties_mixin_getter_gobject_properties, NULL, channel_properties, }, #if nomore { RTCOM_TP_IFACE_CHANNEL_INTERFACE_SMS, tp_dbus_properties_mixin_getter_gobject_properties, NULL, sms_properties, }, #endif { NULL } }; /* ---------------------------------------------------------------------- */ /* Message types to send on this channel */ static TpChannelTextMessageType ring_text_channel_message_types[] = { TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL, /* TP_CHANNEL_TEXT_MESSAGE_TYPE_DELIVERY_REPORT */ }; static char const text_plain[] = "text/plain"; static char const text_vcard[] = "text/x-vcard"; /* vcard 1.0 */ static char const text_vcalendar[] = "text/x-calendar"; /* vcal 1.0 */ /* Supported MIME types for messages mixin */ char const * const * ring_text_get_content_types(void) { static char const * const content_types[] = { text_plain, text_vcard, #if notyet text_vcalendar, #endif NULL }; return content_types; } /* ---------------------------------------------------------------------- */ /* GObject interface for RingTextChannel */ static void ring_text_channel_init(RingTextChannel *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE( self, RING_TYPE_TEXT_CHANNEL, RingTextChannelPrivate); g_queue_init(self->priv->sending); } static void ring_text_channel_constructed(GObject *object) { RingTextChannel *self = RING_TEXT_CHANNEL(object); RingTextChannelPrivate *priv = self->priv; TpBaseConnection *connection = TP_BASE_CONNECTION(priv->connection); TpHandleRepoIface *repo = tp_base_connection_get_handles( connection, TP_HANDLE_TYPE_CONTACT); void (*send)(GObject *, TpMessage *, TpMessageSendingFlags) = ring_text_channel_send; DEBUG("(%p) with %s", self, priv->object_path); if (G_OBJECT_CLASS(ring_text_channel_parent_class)->constructed) G_OBJECT_CLASS(ring_text_channel_parent_class)->constructed(object); tp_handle_ref(repo, priv->handle); priv->target_id = tp_handle_inspect(repo, priv->handle); tp_handle_ref(repo, priv->initiator); priv->destination = ring_text_channel_destination(priv->target_id); dbus_g_connection_register_g_object(tp_get_bus(), priv->object_path, object); tp_message_mixin_init(object, G_STRUCT_OFFSET(RingTextChannel, message), connection); if (G_OBJECT_TYPE(object) != RING_TYPE_TEXT_CHANNEL) { DEBUG("Initializing derived text channel %s", G_OBJECT_TYPE_NAME(object)); } /* Invalid destination - allow channel creation, but refuse sending */ if (!sms_g_is_valid_sms_address(priv->destination)) { DEBUG("Destination '%s' invalid", priv->destination); send = NULL; } /* Flash channel for SMS Class 0 messages */ if (priv->sms_flash) { send = NULL; } tp_message_mixin_implement_sending(object, send, send ? G_N_ELEMENTS(ring_text_channel_message_types) : 0, ring_text_channel_message_types, 0, /* No attachments */ send ? TP_DELIVERY_REPORTING_SUPPORT_FLAG_RECEIVE_FAILURES | TP_DELIVERY_REPORTING_SUPPORT_FLAG_RECEIVE_SUCCESSES : 0, ring_text_get_content_types()); } static void ring_text_channel_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { RingTextChannel *self = RING_TEXT_CHANNEL (object); RingTextChannelPrivate *priv = self->priv; char const *id; switch (property_id) { case PROP_OBJECT_PATH: g_value_set_string (value, priv->object_path); break; case PROP_CHANNEL_DESTROYED: g_value_set_boolean(value, priv->destroyed); break; case PROP_CHANNEL_PROPERTIES: g_value_take_boxed(value, ring_text_channel_properties(self)); break; case PROP_CHANNEL_TYPE: g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_TEXT); break; case PROP_INTERFACES: g_value_set_boxed (value, ring_text_channel_interfaces); break; case PROP_HANDLE_TYPE: g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT); break; case PROP_HANDLE: g_value_set_uint (value, priv->handle); break; case PROP_TARGET_ID: g_value_set_string (value, priv->target_id); break; case PROP_REQUESTED: g_value_set_boolean(value, priv->requested); break; case PROP_INITIATOR: g_value_set_uint (value, priv->initiator); break; case PROP_INITIATOR_ID: id = ring_connection_inspect_contact(priv->connection, priv->initiator); g_value_set_string(value, id); break; case PROP_SMS_FLASH: g_value_set_boolean(value, priv->sms_flash); break; case PROP_TARGET_MATCH: ring_text_channel_set_target_match(value, priv->destination, priv->sms_flash); break; case PROP_SMS_SERVICE: g_value_set_object (value, priv->sms_service); break; case PROP_CONNECTION: g_value_set_object (value, priv->connection); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void ring_text_channel_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { RingTextChannel *self = RING_TEXT_CHANNEL (object); RingTextChannelPrivate *priv = self->priv; switch (property_id) { case PROP_OBJECT_PATH: priv->object_path = g_value_dup_string (value); break; case PROP_REQUESTED: priv->requested = g_value_get_boolean(value); break; case PROP_HANDLE: /* we don't ref handles here because we don't necessarily have access to the * contact repo yet - instead we ref it in the constructed. */ priv->handle = g_value_get_uint(value); break; case PROP_INITIATOR: priv->initiator = g_value_get_uint(value); break; case PROP_HANDLE_TYPE: case PROP_CHANNEL_TYPE: case PROP_INTERFACES: /* these properties are writable in the interface, but not actually * meaningfully changable on this channel, so we do nothing */ break; case PROP_SMS_FLASH: priv->sms_flash = g_value_get_boolean(value); break; case PROP_SMS_SERVICE: priv->sms_service = g_value_get_object (value); break; case PROP_CONNECTION: priv->connection = g_value_get_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void ring_text_channel_dispose(GObject *object) { RingTextChannel *self = RING_TEXT_CHANNEL (object); if (self->priv->disposed) return; self->priv->disposed = TRUE; ring_text_channel_destroy(self); ((GObjectClass *) ring_text_channel_parent_class)->dispose(object); } static void ring_text_channel_finalize(GObject *object) { RingTextChannel *self = RING_TEXT_CHANNEL (object); RingTextChannelPrivate *priv = self->priv; TpBaseConnection *connection = TP_BASE_CONNECTION(priv->connection); TpHandleRepoIface *contact_repo = tp_base_connection_get_handles( connection, TP_HANDLE_TYPE_CONTACT); priv->target_id = NULL; tp_handle_unref(contact_repo, priv->handle), priv->handle = 0; tp_handle_unref(contact_repo, priv->initiator), priv->initiator = 0; g_free(priv->object_path), priv->object_path = NULL; g_free(priv->destination), priv->destination = NULL; while (!g_queue_is_empty(priv->sending)) modem_request_cancel(g_queue_pop_head(priv->sending)); tp_message_mixin_finalize(object); priv->sms_service = NULL; priv->connection = NULL; ((GObjectClass *)ring_text_channel_parent_class)->finalize (object); } static void ring_text_channel_class_init(RingTextChannelClass *klass) { GObjectClass *object_class = (GObjectClass *) klass; g_type_class_add_private(klass, sizeof (RingTextChannelPrivate)); object_class->constructed = ring_text_channel_constructed; object_class->set_property = ring_text_channel_set_property; object_class->get_property = ring_text_channel_get_property; object_class->dispose = ring_text_channel_dispose; object_class->finalize = ring_text_channel_finalize; g_object_class_override_property( object_class, PROP_OBJECT_PATH, "object-path"); g_object_class_override_property( object_class, PROP_CHANNEL_PROPERTIES, "channel-properties"); g_object_class_override_property( object_class, PROP_CHANNEL_DESTROYED, "channel-destroyed"); g_object_class_override_property( object_class, PROP_CHANNEL_TYPE, "channel-type"); g_object_class_override_property( object_class, PROP_HANDLE_TYPE, "handle-type"); g_object_class_override_property( object_class, PROP_HANDLE, "handle"); g_object_class_install_property( object_class, PROP_TARGET_ID, ring_param_spec_handle_id(0)); g_object_class_install_property( object_class, PROP_INTERFACES, ring_param_spec_interfaces()); g_object_class_install_property( object_class, PROP_REQUESTED, ring_param_spec_requested(G_PARAM_CONSTRUCT)); g_object_class_install_property( object_class, PROP_INITIATOR, ring_param_spec_initiator(0)); g_object_class_install_property( object_class, PROP_INITIATOR_ID, ring_param_spec_initiator_id(0)); g_object_class_install_property( object_class, PROP_SMS_FLASH, g_param_spec_boolean("sms-flash", "Channel for Flash SMS Messages", "This channel is only used to receive " "Flash SMS messages", FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property(object_class, PROP_TARGET_MATCH, g_param_spec_string("target-match", "Fuzzy target match", "Value used to fuzzily match channels from same person but different " "contact handle", "", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property( object_class, PROP_CONNECTION, ring_param_spec_connection()); g_object_class_install_property( object_class, PROP_SMS_SERVICE, g_param_spec_object("sms-service", "SMS Service", "Modem SMS Service Object", MODEM_TYPE_SMS_SERVICE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); klass->dbus_properties_class.interfaces = ring_text_channel_dbus_property_interfaces; tp_dbus_properties_mixin_class_init (object_class, G_STRUCT_OFFSET (RingTextChannelClass, dbus_properties_class)); tp_message_mixin_init_dbus_properties(object_class); } /* ---------------------------------------------------------------------- */ static void ring_text_channel_close(RingTextChannel *self) { if (!self->priv->destroyed) { gboolean pending; pending = tp_message_mixin_has_pending_messages((gpointer)self, NULL); if (pending) { tp_message_mixin_set_rescued((gpointer)self); } else { self->priv->destroyed = TRUE; } tp_svc_channel_emit_closed(self); } } static void ring_text_channel_destroy(RingTextChannel *self) { RingTextChannelPrivate *priv = self->priv; if (!priv->destroyed) { priv->destroyed = TRUE; tp_message_mixin_clear((gpointer)self); while (!g_queue_is_empty(priv->sending)) modem_request_cancel(g_queue_pop_head(priv->sending)); tp_svc_channel_emit_closed(self); } } /* ---------------------------------------------------------------------- */ /* org.freedesktop.Telepathy.Channel interface */ static void ring_text_channel_method_close(TpSvcChannel *iface, DBusGMethodInvocation *context) { ring_text_channel_close(RING_TEXT_CHANNEL(iface)); tp_svc_channel_return_from_close(context); } static void ring_text_channel_method_get_channel_type(TpSvcChannel *iface, DBusGMethodInvocation *context) { tp_svc_channel_return_from_get_channel_type(context, TP_IFACE_CHANNEL_TYPE_TEXT); } static void ring_text_channel_method_get_handle(TpSvcChannel *iface, DBusGMethodInvocation *context) { RingTextChannel *self = RING_TEXT_CHANNEL(iface); tp_svc_channel_return_from_get_handle(context, TP_HANDLE_TYPE_CONTACT, self->priv->handle); } static void ring_text_channel_method_get_interfaces(TpSvcChannel *iface, DBusGMethodInvocation *context) { char const ** interfaces = (gchar const **)ring_text_channel_interfaces; tp_svc_channel_return_from_get_interfaces(context, interfaces); } static void channel_iface_init(gpointer iface, gpointer data) { TpSvcChannelClass *klass = iface; #define IMPLEMENT(x) tp_svc_channel_implement_##x(klass, ring_text_channel_method_##x) IMPLEMENT(close); IMPLEMENT(get_channel_type); IMPLEMENT(get_handle); IMPLEMENT(get_interfaces); #undef IMPLEMENT } /* ====================================================================== */ /* Channel.Interface.Destroyable */ static void ring_text_channel_method_destroy(TpSvcChannelInterfaceDestroyable *iface, DBusGMethodInvocation *context) { ring_text_channel_destroy(RING_TEXT_CHANNEL(iface)); tp_svc_channel_interface_destroyable_return_from_destroy(context); } static void ring_text_channel_destroyable_iface_init(gpointer iface, gpointer data) { TpSvcChannelInterfaceDestroyableClass *klass = iface; #define IMPLEMENT(x) \ tp_svc_channel_interface_destroyable_implement_##x \ (klass, ring_text_channel_method_ ## x) IMPLEMENT(destroy); #undef IMPLEMENT } /* ---------------------------------------------------------------------- */ /* message_mixin interface */ static GValue const * my_message_mixin_get_value(TpMessage const *message, guint part, char const *key) { GHashTable const *dict; dict = tp_message_peek((TpMessage *)message, part); if (!dict) return NULL; return g_hash_table_lookup((GHashTable *)dict, key); } static char const * my_message_mixin_get_string(TpMessage const *message, guint part, char const *key, char const *defaults) { GValue const *value = my_message_mixin_get_value(message, part, key); if (value == NULL || !G_VALUE_HOLDS_STRING(value)) return defaults; return g_value_get_string(value); } #if nomore static GArray const * my_message_mixin_get_bytearray(TpMessage const *message, guint part, char const *key, GArray const *defaults) { GValue const *value = my_message_mixin_get_value(message, part, key); if (value == NULL || !G_VALUE_HOLDS(value, DBUS_TYPE_G_UCHAR_ARRAY)) return defaults; return g_value_get_boxed(value); } static guint32 my_message_mixin_get_uint(TpMessage const *message, guint part, char const *key, guint32 defaults) { GValue const *value = my_message_mixin_get_value(message, part, key); if (value == NULL || !G_VALUE_HOLDS_UINT(value)) return defaults; return g_value_get_uint(value); } #endif /** Convert handle inspection to a destination acceptable by modem. * * Remove supported service prefixes and dial strings. */ char * ring_text_channel_destination(char const *inspection) { if (modem_call_is_valid_address(inspection)) { /* Ignore prefix to suppress CLIR (*31#) */ if (ring_str_starts_with(inspection, "*31#")) inspection += 4; /* Ignore dialstring */ return g_strndup(inspection, strcspn(inspection, "PXw")); } else { return g_strdup(""); } } static void ring_text_channel_set_target_match(GValue *value, char const *id, int flash) { size_t idlen = strlen(id); /* sms_g_is_valid_sms_address() fails for IA5 names */ if (idlen <= 6 || !sms_g_is_valid_sms_address(id)) ; else id = id + idlen - 6; if (flash) g_value_take_string(value, g_strdup_printf("sms-class-0-%s", id)); else g_value_set_string(value, id); } static void ring_text_channel_send(GObject *_self, TpMessage *msg, TpMessageSendingFlags flags) { RingTextChannel *self = RING_TEXT_CHANNEL(_self); RingTextChannelPrivate *priv = self->priv; #if nomore gboolean srr; guint32 sms_class; char const *smsc; #endif char const *type; char const *text; ModemRequest *request; GError *error; g_assert(tp_message_count_parts(msg) >= 1); error = NULL; type = my_message_mixin_get_string(msg, 1, "content-type", ""); if (!type[0]) type = my_message_mixin_get_string(msg, 1, "type", ""); if (!type[0]) { GError invalid = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "No content type" }; tp_message_mixin_sent(_self, msg, flags, NULL, &invalid); return; } if (priv->sms_service == NULL) { GError failed = { TP_ERRORS, TP_ERROR_NETWORK_ERROR, "Modem connection failed" }; tp_message_mixin_sent(_self, msg, flags, NULL, &failed); return; } /* The nomore'd stuff is currently not supported by Ofono */ #if nomore /* Status report request */ srr = (flags & TP_MESSAGE_SENDING_FLAG_REPORT_DELIVERY) != 0; sms_class = my_message_mixin_get_uint(msg, 0, "sms-class", 0xff); if (0 <= sms_class && sms_class <= 3) sms_g_submit_set_sms_class(submit, sms_class); smsc = my_message_mixin_get_string(msg, 0, "sms-service-centre", NULL); if (smsc == NULL || strlen(smsc) == 0) smsc = my_message_mixin_get_string(msg, 0, "sms-smsc", NULL); if (smsc == NULL || strlen(smsc) == 0) smsc = my_message_mixin_get_string(msg, 0, "smsc", NULL); if (smsc != NULL && strlen(smsc) != 0) sms_g_submit_set_smsc(submit, smsc); #endif text = my_message_mixin_get_string(msg, 1, "content", ""); if (g_strcasecmp(type, text_plain) == 0) { DEBUG("Send(destination = %s," /*class = %u,*/ "text = \"%s\")", priv->target_id, /*sms_class,*/ text); } #if nomore else if (g_strcasecmp(type, "text/x-vcard") == 0 || g_strcasecmp(type, "text/directory") == 0 || g_strcasecmp(type, "text/vcard") == 0) { GArray b = { (gpointer)text, strlen(text) }; GArray const *binary; binary = my_message_mixin_get_bytearray(msg, 1, "content", &b); if (binary && binary->data) { encoded = sms_g_submit_binary(submit, binary, &error); } else { g_set_error(&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "No content"); } } #endif else { g_set_error(&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "Unknown content type"); } request = modem_sms_request_send(priv->sms_service, priv->target_id, text, modem_sms_request_send_reply, self); if (request == NULL) { GError failed = { TP_ERRORS, TP_ERROR_NETWORK_ERROR, "Modem connection failed" }; tp_message_mixin_sent(_self, msg, flags, NULL, &failed); return; } modem_request_add_data(request, "tp-message", msg); modem_request_add_data(request, "tp-flags", GUINT_TO_POINTER(flags)); g_queue_push_tail(priv->sending, request); } static void modem_sms_request_send_reply(ModemSMSService *service, ModemRequest *request, char const *token, GError const *send_error, gpointer _self) { RingTextChannel *self = RING_TEXT_CHANNEL(_self); RingTextChannelPrivate *priv = self->priv; TpMessage *msg = modem_request_get_data(request, "tp-message"); GError *error = NULL; guint flags = GPOINTER_TO_UINT(modem_request_get_data(request, "tp-flags")); g_assert(msg); g_queue_remove(priv->sending, request); if (!send_error) { DEBUG("Send(%p) token=\"%s\"", msg, token); tp_message_set_int64(msg, 0, "message-sent", (gint64)time(NULL)); } else { if (send_error && send_error->domain == DBUS_GERROR) g_set_error_literal(&error, TP_ERRORS, TP_ERROR_NETWORK_ERROR, send_error->message); else if (send_error) error = g_error_copy(send_error); else g_set_error_literal(&error, TP_ERRORS, TP_ERROR_NETWORK_ERROR, "Internal error"); DEBUG("Send(%p) GError(%u, '%s, %s)", msg, error->code, g_quark_to_string(error->domain), error->message); } tp_message_mixin_sent((GObject *)self, msg, flags, token, error); } /* ------------------------------------------------------------------------ */ /* RingTextChannel interface */ gboolean ring_text_channel_can_handle(gpointer sms) { return sms_g_deliver_is_text(sms) || sms_g_deliver_is_vcard(sms); } void ring_text_channel_receive_deliver(RingTextChannel *self, gpointer sms) { TpMessage *msg; char const *message_token = sms_g_deliver_get_message_token(sms); guint id; DEBUG("enter"); g_assert (ring_text_channel_can_handle(sms)); msg = tp_message_new((TpBaseConnection *)self->priv->connection, 2, 2); tp_message_set_handle(msg, 0, "message-sender", TP_HANDLE_TYPE_CONTACT, self->priv->handle); tp_message_set_string(msg, 0, "message-token", message_token); ring_text_channel_set_receive_timestamps(self, msg, sms); guint32 sms_class = sms_g_deliver_get_sms_class(sms); if (0 <= sms_class && sms_class <= 3) { tp_message_set_uint32(msg, 0, "sms-class", sms_class); } tp_message_set_string(msg, 0, "sms-service-centre", sms_g_deliver_get_smsc(sms)); { char *mwi_type = NULL; guint mwi_line = 0, mwi_messages = 0; gboolean mwi_active = FALSE, mwi_discard = FALSE; TpChannelTextMessageType msg_type; g_object_get(sms, "mwi-type", &mwi_type, "mwi-active", &mwi_active, "mwi-discard", &mwi_discard, "mwi-line", &mwi_line, "mwi-messages", &mwi_messages, NULL); if (mwi_type) { msg_type = TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE; #if nomore /* XXX: waiting for upstream tp-glib to get these */ if (g_str_equal(mwi_type, "voice")) tp_message_set_string(msg, 0, NOKIA_VOICEMAIL_TYPE, "tel"); tp_message_set_string(msg, 0, "sms-mwi-type", mwi_type); tp_message_set_string(msg, 0, NOKIA_MAILBOX_NOTIFICATION, mwi_type); if (strcmp(mwi_type, "return-call")) { tp_message_set_boolean(msg, 0, NOKIA_MAILBOX_HAS_UNREAD, mwi_active); tp_message_set_boolean(msg, 0, "sms-mwi-active", mwi_active); if (mwi_line) tp_message_set_uint32(msg, 0, "sms-mwi-line", mwi_line); if (mwi_messages > 0 && mwi_messages < 255) { tp_message_set_uint32(msg, 0, "sms-mwi-messages", mwi_messages); tp_message_set_uint32(msg, 0, NOKIA_MAILBOX_UNREAD_COUNT, mwi_messages); } if (mwi_discard) { tp_message_set_boolean(msg, 0, "sms-mwi-discard", mwi_discard); tp_message_set_boolean(msg, 0, NOKIA_MAILBOX_DISCARD_TEXT, mwi_discard); } } #endif g_free(mwi_type); } else { msg_type = TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL; } tp_message_set_uint32(msg, 0, "message-type", msg_type); } gboolean string = FALSE, bytes = FALSE; if (sms_g_deliver_is_text(sms)) { tp_message_set_string(msg, 1, "content-type", text_plain); tp_message_set_string(msg, 1, "type", text_plain); string = TRUE; } else if (sms_g_deliver_is_vcard(sms)) { tp_message_set_string(msg, 1, "content-type", text_vcard); tp_message_set_string(msg, 1, "type", text_vcard); bytes = TRUE; } #if notyet else if (sms_g_deliver_is_vcalendar(sms)) { tp_message_set_string(msg, 1, "content-type", text_vcalendar); tp_message_set_string(msg, 1, "type", text_vcalendar); bytes = TRUE; } #endif GArray const *binary = sms_g_deliver_get_binary(sms); char const *text = sms_g_deliver_get_text(sms); if (string) { if (text) { tp_message_set_string(msg, 1, "content", text); } else { tp_message_set_string(msg, 1, "content", ""); } } else if (bytes) { if (binary) { tp_message_set_bytes(msg, 1, "content", binary->len, binary->data); } else if (text) { tp_message_set_bytes(msg, 1, "content", strlen(text), text); } else { tp_message_set_bytes(msg, 1, "content", 0, ""); } } id = tp_message_mixin_take_received((GObject *) self, msg); DEBUG("message mixin received with id=%u", id); } static void ring_text_channel_delivery_report(RingTextChannel *self, char const *token, guint delivery_status, gpointer sr, GError const *error) { TpMessage *msg; guint id; msg = tp_message_new((TpBaseConnection *)self->priv->connection, 1, 1); tp_message_set_handle(msg, 0, "message-sender", TP_HANDLE_TYPE_CONTACT, self->priv->handle); tp_message_set_uint32(msg, 0, "message-type", TP_CHANNEL_TEXT_MESSAGE_TYPE_DELIVERY_REPORT); tp_message_set_string(msg, 0, "delivery-token", token); if (delivery_status != TP_DELIVERY_STATUS_UNKNOWN) tp_message_set_uint32(msg, 0, "delivery-status", delivery_status); if (sr) { char const *message_token = sms_g_status_report_get_message_token(sr); guint8 failure_cause = sms_g_status_report_get_status(sr); tp_message_set_string(msg, 0, "message-token", message_token); tp_message_set_string(msg, 0, "sms-service-centre", sms_g_status_report_get_smsc(sr)); tp_message_set_uint32(msg, 0, "sms-failure-cause", failure_cause); ring_text_channel_set_receive_timestamps(self, msg, sr); } else { GTimeVal gt[1]; g_get_current_time(gt); tp_message_set_int64(msg, 0, "message-received", (gint64)gt->tv_sec); tp_message_set_int64(msg, 0, "message-sent", (gint64)gt->tv_sec); tp_message_set_string_printf(msg, 0, "message-token", "%s-%08ld-%08ld", token, gt->tv_sec, gt->tv_usec); } if (error) { char const *prefix = modem_error_domain_prefix(error->domain); if (prefix && prefix[0]) { char ebuffer[16]; tp_message_set_string_printf(msg, 0, "delivery-dbus-error", "%s.%s", prefix, modem_error_name(error, ebuffer, sizeof ebuffer)); tp_message_set_string(msg, 0, "delivery-error-message", error->message); } else if (error->domain == TP_ERRORS) { GEnumClass *klass = g_type_class_ref(TP_TYPE_ERROR); GEnumValue *ev = g_enum_get_value (klass, error->code); g_type_class_unref(klass); tp_message_set_string_printf(msg, 0, "delivery-dbus-error", "%s.%s", TP_ERROR_PREFIX, ev->value_nick); tp_message_set_string(msg, 0, "delivery-error-message", error->message); } else { tp_message_set_string_printf(msg, 0, "delivery-error-message", "%s %u: %s", g_quark_to_string(error->domain), error->code, error->message); } } id = tp_message_mixin_take_received((GObject *) self, msg); DEBUG("delivery report received with id=%u", id); } static void ring_text_channel_set_receive_timestamps(RingTextChannel *self, TpMessage *msg, gpointer sms) { g_return_if_fail(SMS_G_IS_MESSAGE(sms)); gint64 sent = 0, received = 0, delivered = 0; gint64 now = (gint64)time(NULL); g_object_get(sms, "time-sent", &sent, "time-received", &received, "time-delivered", &delivered, NULL); tp_message_set_int64(msg, 0, "message-sent", sent); tp_message_set_int64(msg, 0, "message-received", received); if (delivered == 0) { tp_message_set_uint64(msg, 0, "stored-message-received", received); } else { tp_message_set_uint64(msg, 0, "stored-message-received", delivered); if (delivered > modem_sms_service_time_connected(self->priv->sms_service)) tp_message_set_boolean(msg, 0, "rescued", TRUE); } g_object_set(sms, "time-delivered", now, NULL); } void ring_text_channel_outgoing_sms_complete(RingTextChannel *self, char const *token) { ring_text_channel_delivery_report(self, token, TP_DELIVERY_STATUS_ACCEPTED, NULL, NULL); } void ring_text_channel_outgoing_sms_error(RingTextChannel *self, char const *token, GError const *error) { guint delivery_status; if (0 /*modem_sms_error_is_temporary(error)*/) delivery_status = TP_DELIVERY_STATUS_TEMPORARILY_FAILED; else delivery_status = TP_DELIVERY_STATUS_PERMANENTLY_FAILED; ring_text_channel_delivery_report(self, token, delivery_status, NULL, error); } void ring_text_channel_receive_status_report(RingTextChannel *self, gpointer sr) { guint delivery_status; if (sms_g_status_report_is_status_completed(sr)) delivery_status = TP_DELIVERY_STATUS_DELIVERED; else if (sms_g_status_report_is_status_permanent(sr)) delivery_status = TP_DELIVERY_STATUS_PERMANENTLY_FAILED; else if (sms_g_status_report_is_status_temporary(sr)) delivery_status = TP_DELIVERY_STATUS_TEMPORARILY_FAILED; else delivery_status = TP_DELIVERY_STATUS_UNKNOWN; char const *token = sms_g_status_report_get_delivery_token(sr); if (token == NULL) token = sms_g_status_report_get_message_token(sr); ring_text_channel_delivery_report(self, token, delivery_status, sr, NULL); }