/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details: * * Copyright (C) 2008 - 2009 Novell, Inc. * Copyright (C) 2009 - 2012 Red Hat, Inc. * Copyright (C) 2012 Google, Inc. */ #include #include #include #include #include #include #include #include #include "mm-iface-modem.h" #include "mm-iface-modem-messaging.h" #include "mm-sms.h" #include "mm-base-modem-at.h" #include "mm-base-modem.h" #include "mm-utils.h" #include "mm-log.h" #include "mm-modem-helpers.h" G_DEFINE_TYPE (MMSms, mm_sms, MM_GDBUS_TYPE_SMS_SKELETON); enum { PROP_0, PROP_PATH, PROP_CONNECTION, PROP_MODEM, PROP_IS_MULTIPART, PROP_MAX_PARTS, PROP_MULTIPART_REFERENCE, PROP_LAST }; static GParamSpec *properties[PROP_LAST]; struct _MMSmsPrivate { /* The connection to the system bus */ GDBusConnection *connection; /* The modem which owns this SMS */ MMBaseModem *modem; /* The path where the SMS object is exported */ gchar *path; /* Multipart SMS specific stuff */ gboolean is_multipart; guint multipart_reference; /* List of SMS parts */ guint max_parts; GList *parts; /* Set to true when all needed parts were received, * parsed and assembled */ gboolean is_assembled; }; /*****************************************************************************/ /* Store SMS (DBus call handling) */ typedef struct { MMSms *self; MMBaseModem *modem; GDBusMethodInvocation *invocation; } HandleStoreContext; static void handle_store_context_free (HandleStoreContext *ctx) { g_object_unref (ctx->invocation); g_object_unref (ctx->modem); g_object_unref (ctx->self); g_free (ctx); } static void handle_store_ready (MMSms *self, GAsyncResult *res, HandleStoreContext *ctx) { GError *error = NULL; if (!MM_SMS_GET_CLASS (self)->store_finish (self, res, &error)) g_dbus_method_invocation_take_error (ctx->invocation, error); else { MMSmsStorage storage = MM_SMS_STORAGE_UNKNOWN; /* We'll set now the proper storage, taken from the default mem2 one */ g_object_get (self->priv->modem, MM_IFACE_MODEM_MESSAGING_SMS_MEM2_STORAGE, &storage, NULL); g_object_set (self, "storage", storage, NULL); mm_gdbus_sms_complete_store (MM_GDBUS_SMS (ctx->self), ctx->invocation); } handle_store_context_free (ctx); } static gboolean sms_is_stored (MMSms *self) { GList *l; for (l = self->priv->parts; l; l = g_list_next (l)) { if (mm_sms_part_get_index ((MMSmsPart *)l->data) == SMS_PART_INVALID_INDEX) return FALSE; } return TRUE; } static void handle_store_auth_ready (MMBaseModem *modem, GAsyncResult *res, HandleStoreContext *ctx) { GError *error = NULL; if (!mm_base_modem_authorize_finish (modem, res, &error)) { g_dbus_method_invocation_take_error (ctx->invocation, error); handle_store_context_free (ctx); return; } /* First of all, check if we already have the SMS stored. */ if (sms_is_stored (ctx->self)) { mm_gdbus_sms_complete_store (MM_GDBUS_SMS (ctx->self), ctx->invocation); handle_store_context_free (ctx); return; } /* If not stored, check if we do support doing it */ if (!MM_SMS_GET_CLASS (ctx->self)->store || !MM_SMS_GET_CLASS (ctx->self)->store_finish) { g_dbus_method_invocation_return_error (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Storing SMS is not supported by this modem"); handle_store_context_free (ctx); return; } MM_SMS_GET_CLASS (ctx->self)->store (ctx->self, (GAsyncReadyCallback)handle_store_ready, ctx); } static gboolean handle_store (MMSms *self, GDBusMethodInvocation *invocation) { HandleStoreContext *ctx; ctx = g_new0 (HandleStoreContext, 1); ctx->self = g_object_ref (self); ctx->invocation = g_object_ref (invocation); g_object_get (self, MM_SMS_MODEM, &ctx->modem, NULL); mm_base_modem_authorize (ctx->modem, invocation, MM_AUTHORIZATION_MESSAGING, (GAsyncReadyCallback)handle_store_auth_ready, ctx); return TRUE; } /*****************************************************************************/ /* Send SMS (DBus call handling) */ typedef struct { MMSms *self; MMBaseModem *modem; GDBusMethodInvocation *invocation; } HandleSendContext; static void handle_send_context_free (HandleSendContext *ctx) { g_object_unref (ctx->invocation); g_object_unref (ctx->modem); g_object_unref (ctx->self); g_free (ctx); } static void handle_send_ready (MMSms *self, GAsyncResult *res, HandleSendContext *ctx) { GError *error = NULL; if (!MM_SMS_GET_CLASS (self)->send_finish (self, res, &error)) g_dbus_method_invocation_take_error (ctx->invocation, error); else mm_gdbus_sms_complete_send (MM_GDBUS_SMS (ctx->self), ctx->invocation); handle_send_context_free (ctx); } static void handle_send_auth_ready (MMBaseModem *modem, GAsyncResult *res, HandleSendContext *ctx) { MMSmsState state; GError *error = NULL; if (!mm_base_modem_authorize_finish (modem, res, &error)) { g_dbus_method_invocation_take_error (ctx->invocation, error); handle_send_context_free (ctx); return; } /* We can only send SMS created by the user */ state = mm_gdbus_sms_get_state (MM_GDBUS_SMS (ctx->self)); if (state == MM_SMS_STATE_RECEIVED || state == MM_SMS_STATE_RECEIVING) { g_dbus_method_invocation_return_error (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "This SMS was received, cannot send it"); handle_send_context_free (ctx); return; } /* Check if we do support doing it */ if (!MM_SMS_GET_CLASS (ctx->self)->send || !MM_SMS_GET_CLASS (ctx->self)->send_finish) { g_dbus_method_invocation_return_error (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Sending SMS is not supported by this modem"); handle_send_context_free (ctx); return; } MM_SMS_GET_CLASS (ctx->self)->send (ctx->self, (GAsyncReadyCallback)handle_send_ready, ctx); } static gboolean handle_send (MMSms *self, GDBusMethodInvocation *invocation) { HandleSendContext *ctx; ctx = g_new0 (HandleSendContext, 1); ctx->self = g_object_ref (self); ctx->invocation = g_object_ref (invocation); g_object_get (self, MM_SMS_MODEM, &ctx->modem, NULL); mm_base_modem_authorize (ctx->modem, invocation, MM_AUTHORIZATION_MESSAGING, (GAsyncReadyCallback)handle_send_auth_ready, ctx); return TRUE; } /*****************************************************************************/ void mm_sms_export (MMSms *self) { static guint id = 0; gchar *path; path = g_strdup_printf (MM_DBUS_SMS_PREFIX "/%d", id++); g_object_set (self, MM_SMS_PATH, path, NULL); g_free (path); } void mm_sms_unexport (MMSms *self) { g_object_set (self, MM_SMS_PATH, NULL, NULL); } /*****************************************************************************/ static void mm_sms_dbus_export (MMSms *self) { GError *error = NULL; /* Handle method invocations */ g_signal_connect (self, "handle-store", G_CALLBACK (handle_store), NULL); g_signal_connect (self, "handle-send", G_CALLBACK (handle_send), NULL); if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self), self->priv->connection, self->priv->path, &error)) { mm_warn ("couldn't export SMS at '%s': '%s'", self->priv->path, error->message); g_error_free (error); } } static void mm_sms_dbus_unexport (MMSms *self) { /* Only unexport if currently exported */ if (g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (self))) g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self)); } /*****************************************************************************/ const gchar * mm_sms_get_path (MMSms *self) { return self->priv->path; } MMSmsStorage mm_sms_get_storage (MMSms *self) { MMSmsStorage storage = MM_SMS_STORAGE_UNKNOWN; g_object_get (self, "storage", &storage, NULL); return storage; } gboolean mm_sms_is_multipart (MMSms *self) { return self->priv->is_multipart; } guint mm_sms_get_multipart_reference (MMSms *self) { g_return_val_if_fail (self->priv->is_multipart, 0); return self->priv->multipart_reference; } gboolean mm_sms_multipart_is_complete (MMSms *self) { return (g_list_length (self->priv->parts) == self->priv->max_parts); } gboolean mm_sms_multipart_is_assembled (MMSms *self) { return self->priv->is_assembled; } /*****************************************************************************/ static guint cmp_sms_part_index (MMSmsPart *part, gpointer user_data) { return (GPOINTER_TO_UINT (user_data) - mm_sms_part_get_index (part)); } gboolean mm_sms_has_part_index (MMSms *self, guint index) { return !!g_list_find_custom (self->priv->parts, GUINT_TO_POINTER (index), (GCompareFunc)cmp_sms_part_index); } /*****************************************************************************/ static gchar * sms_get_store_or_send_command (MMSmsPart *part, gboolean text_or_pdu, /* TRUE for PDU */ gboolean store_or_send, /* TRUE for send */ GError **error) { gchar *cmd; if (!text_or_pdu) { /* Text mode */ cmd = g_strdup_printf ("+CMG%c=\"%s\"\r%s\x1a", store_or_send ? 'S' : 'W', mm_sms_part_get_number (part), mm_sms_part_get_text (part)); } else { guint8 *pdu; guint pdulen = 0; guint msgstart = 0; gchar *hex; /* AT+CMGW=[, ] PDU can be entered. / */ pdu = mm_sms_part_get_submit_pdu (part, &pdulen, &msgstart, error); if (!pdu) return NULL; /* Convert PDU to hex */ hex = utils_bin2hexstr (pdu, pdulen); g_free (pdu); if (!hex) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Not enough memory to send SMS PDU"); return NULL; } /* CMGW/S length is the size of the PDU without SMSC information */ cmd = g_strdup_printf ("+CMG%c=%d\r%s\x1a", store_or_send ? 'S' : 'W', pdulen - msgstart, hex); g_free (hex); } return cmd; } /*****************************************************************************/ /* Store the SMS */ typedef struct { MMSms *self; MMBaseModem *modem; GSimpleAsyncResult *result; } SmsStoreContext; static void sms_store_context_complete_and_free (SmsStoreContext *ctx) { g_simple_async_result_complete_in_idle (ctx->result); g_object_unref (ctx->result); g_object_unref (ctx->modem); g_object_unref (ctx->self); g_free (ctx); } static gboolean sms_store_finish (MMSms *self, GAsyncResult *res, GError **error) { return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); } static void store_ready (MMBaseModem *modem, GAsyncResult *res, SmsStoreContext *ctx) { const gchar *response; GError *error = NULL; gint rv; gint idx; response = mm_base_modem_at_command_finish (MM_BASE_MODEM (modem), res, &error); if (error) { g_simple_async_result_take_error (ctx->result, error); sms_store_context_complete_and_free (ctx); return; } /* Read the new part index from the reply */ rv = sscanf (response, "+CMGW: %d", &idx); if (rv != 1 || idx < 0) { g_simple_async_result_set_error (ctx->result, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read index of already stored part: " "%d fields parsed", rv); sms_store_context_complete_and_free (ctx); return; } /* Set the index in the part we hold */ mm_sms_part_set_index ((MMSmsPart *)ctx->self->priv->parts->data, (guint)idx); g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); sms_store_context_complete_and_free (ctx); } static void sms_store (MMSms *self, GAsyncReadyCallback callback, gpointer user_data) { GError *error = NULL; gboolean use_pdu_mode; SmsStoreContext *ctx; gchar *cmd; /* We currently support storing *only* single part SMS */ if (g_list_length (self->priv->parts) != 1) { g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Cannot store SMS with %u parts", g_list_length (self->priv->parts)); return; } /* Setup the context */ ctx = g_new0 (SmsStoreContext, 1); ctx->result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, sms_store); ctx->self = g_object_ref (self); ctx->modem = g_object_ref (self->priv->modem); /* Different ways to do it if on PDU or text mode */ use_pdu_mode = FALSE; g_object_get (self->priv->modem, MM_IFACE_MODEM_MESSAGING_SMS_PDU_MODE, &use_pdu_mode, NULL); cmd = sms_get_store_or_send_command ((MMSmsPart *)ctx->self->priv->parts->data, use_pdu_mode, FALSE, &error); if (!cmd) { g_simple_async_result_take_error (ctx->result, error); sms_store_context_complete_and_free (ctx); return; } mm_base_modem_at_command (ctx->modem, cmd, 10, FALSE, (GAsyncReadyCallback) store_ready, ctx); g_free (cmd); } /*****************************************************************************/ /* Send the SMS */ typedef struct { MMSms *self; MMBaseModem *modem; GSimpleAsyncResult *result; } SmsSendContext; static void sms_send_context_complete_and_free (SmsSendContext *ctx) { g_simple_async_result_complete_in_idle (ctx->result); g_object_unref (ctx->result); g_object_unref (ctx->modem); g_object_unref (ctx->self); g_free (ctx); } static gboolean sms_send_finish (MMSms *self, GAsyncResult *res, GError **error) { return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); } static void send_generic_ready (MMBaseModem *modem, GAsyncResult *res, SmsSendContext *ctx) { GError *error = NULL; mm_base_modem_at_command_finish (MM_BASE_MODEM (modem), res, &error); if (error) { g_simple_async_result_take_error (ctx->result, error); sms_send_context_complete_and_free (ctx); return; } g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); sms_send_context_complete_and_free (ctx); } static void sms_send_generic (SmsSendContext *ctx) { gchar *cmd; GError *error = NULL; gboolean use_pdu_mode = FALSE; /* Different ways to do it if on PDU or text mode */ g_object_get (ctx->self->priv->modem, MM_IFACE_MODEM_MESSAGING_SMS_PDU_MODE, &use_pdu_mode, NULL); cmd = sms_get_store_or_send_command ((MMSmsPart *)ctx->self->priv->parts->data, use_pdu_mode, FALSE, &error); if (!cmd) { g_simple_async_result_take_error (ctx->result, error); sms_send_context_complete_and_free (ctx); return; } mm_base_modem_at_command (ctx->modem, cmd, 10, FALSE, (GAsyncReadyCallback)send_generic_ready, ctx); g_free (cmd); } static void send_from_storage_ready (MMBaseModem *modem, GAsyncResult *res, SmsSendContext *ctx) { GError *error = NULL; mm_base_modem_at_command_finish (MM_BASE_MODEM (modem), res, &error); if (error) { mm_dbg ("Couldn't send SMS from storage: '%s'; trying generic send...", error->message); g_error_free (error); sms_send_generic (ctx); return; } g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); sms_send_context_complete_and_free (ctx); } static void sms_send_from_storage (SmsSendContext *ctx) { gchar *cmd; cmd = g_strdup_printf ("+CMSS=%d", mm_sms_part_get_index ((MMSmsPart *)ctx->self->priv->parts->data)); mm_base_modem_at_command (ctx->modem, cmd, 10, FALSE, (GAsyncReadyCallback)send_from_storage_ready, ctx); g_free (cmd); } static void sms_send (MMSms *self, GAsyncReadyCallback callback, gpointer user_data) { SmsSendContext *ctx; /* We currently support storing *only* single part SMS */ if (g_list_length (self->priv->parts) != 1) { g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Cannot send SMS with %u parts", g_list_length (self->priv->parts)); return; } /* Setup the context */ ctx = g_new0 (SmsSendContext, 1); ctx->result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, sms_send); ctx->self = g_object_ref (self); ctx->modem = g_object_ref (self->priv->modem); /* If the part is STORED, try to send from storage */ if (mm_sms_part_get_index ((MMSmsPart *)self->priv->parts->data) != SMS_PART_INVALID_INDEX) sms_send_from_storage (ctx); else sms_send_generic (ctx); } /*****************************************************************************/ typedef struct { MMSms *self; MMBaseModem *modem; GSimpleAsyncResult *result; GList *current; guint n_failed; } SmsDeletePartsContext; static void sms_delete_parts_context_complete_and_free (SmsDeletePartsContext *ctx) { g_simple_async_result_complete_in_idle (ctx->result); g_object_unref (ctx->result); g_object_unref (ctx->modem); g_object_unref (ctx->self); g_free (ctx); } static gboolean sms_delete_finish (MMSms *self, GAsyncResult *res, GError **error) { return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); } static void delete_next_part (SmsDeletePartsContext *ctx); static void delete_part_ready (MMBaseModem *modem, GAsyncResult *res, SmsDeletePartsContext *ctx) { GError *error = NULL; mm_base_modem_at_command_finish (MM_BASE_MODEM (modem), res, &error); if (error) { ctx->n_failed++; mm_dbg ("Couldn't delete SMS part with index %u: '%s'", mm_sms_part_get_index ((MMSmsPart *)ctx->current->data), error->message); g_error_free (error); } /* We reset the index, as there is no longer that part */ mm_sms_part_set_index ((MMSmsPart *)ctx->current->data, SMS_PART_INVALID_INDEX); ctx->current = g_list_next (ctx->current); delete_next_part (ctx); } static void delete_next_part (SmsDeletePartsContext *ctx) { gchar *cmd; /* Skip non-stored parts */ while (ctx->current && mm_sms_part_get_index ((MMSmsPart *)ctx->current->data) == SMS_PART_INVALID_INDEX) ctx->current = g_list_next (ctx->current); /* If all removed, we're done */ if (!ctx->current) { if (ctx->n_failed > 0) g_simple_async_result_set_error (ctx->result, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't delete %u parts from this SMS", ctx->n_failed); else { /* We do change the state of this SMS back to UNKNOWN, as it is no * longer stored in the device */ g_object_set (ctx->self, "state", MM_SMS_STATE_UNKNOWN, NULL); g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); } sms_delete_parts_context_complete_and_free (ctx); return; } cmd = g_strdup_printf ("+CMGD=%d", mm_sms_part_get_index ((MMSmsPart *)ctx->current->data)); mm_base_modem_at_command (ctx->modem, cmd, 10, FALSE, (GAsyncReadyCallback)delete_part_ready, ctx); g_free (cmd); } static void sms_delete (MMSms *self, GAsyncReadyCallback callback, gpointer user_data) { SmsDeletePartsContext *ctx; ctx = g_new0 (SmsDeletePartsContext, 1); ctx->result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, sms_delete); ctx->self = g_object_ref (self); ctx->modem = g_object_ref (self->priv->modem); /* Go on deleting parts */ ctx->current = ctx->self->priv->parts; delete_next_part (ctx); } /*****************************************************************************/ gboolean mm_sms_delete_finish (MMSms *self, GAsyncResult *res, GError **error) { if (MM_SMS_GET_CLASS (self)->delete_finish) return MM_SMS_GET_CLASS (self)->delete_finish (self, res, error); return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); } void mm_sms_delete (MMSms *self, GAsyncReadyCallback callback, gpointer user_data) { if (MM_SMS_GET_CLASS (self)->delete && MM_SMS_GET_CLASS (self)->delete_finish) { MM_SMS_GET_CLASS (self)->delete (self, callback, user_data); return; } g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Deleting SMS is not supported by this modem"); } /*****************************************************************************/ static gboolean assemble_sms (MMSms *self, GError **error) { GList *l; gchar **textparts; guint idx; gchar *fulltext; MMSmsPart *first = NULL; /* Assemble text from all parts */ textparts = g_malloc0 ((1 + self->priv->max_parts) * sizeof (* textparts)); for (l = self->priv->parts; l; l = g_list_next (l)) { idx = mm_sms_part_get_concat_sequence ((MMSmsPart *)l->data); if (textparts[idx]) { mm_warn ("Duplicate part index (%u) found, ignoring", idx); continue; } /* NOTE! we don't strdup here */ textparts[idx] = (gchar *)mm_sms_part_get_text ((MMSmsPart *)l->data); /* If first in multipart, keep it for later */ if (idx == 0) first = (MMSmsPart *)l->data; } /* Check if we have all parts */ for (idx = 0; idx < self->priv->max_parts; idx++) { if (!textparts[idx]) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Cannot assemble SMS, missing part at index %u", idx); g_free (textparts); return FALSE; } } /* If we got all parts, we also have the first one always */ g_assert (first != NULL); /* If we got everything, assemble the text! */ fulltext = g_strjoinv (NULL, textparts); g_object_set (self, "text", fulltext, "smsc", mm_sms_part_get_smsc (first), "class", mm_sms_part_get_class (first), "number", mm_sms_part_get_number (first), "timestamp", mm_sms_part_get_timestamp (first), "validity", mm_sms_part_get_validity (first), NULL); g_free (fulltext); g_free (textparts); self->priv->is_assembled = TRUE; return TRUE; } /*****************************************************************************/ static guint cmp_sms_part_sequence (MMSmsPart *a, MMSmsPart *b) { return (mm_sms_part_get_concat_sequence (a) - mm_sms_part_get_concat_sequence (b)); } gboolean mm_sms_multipart_take_part (MMSms *self, MMSmsPart *part, GError **error) { if (!self->priv->is_multipart) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "This SMS is not a multipart message"); return FALSE; } if (g_list_length (self->priv->parts) >= self->priv->max_parts) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Already took %u parts, cannot take more", g_list_length (self->priv->parts)); return FALSE; } if (g_list_find_custom (self->priv->parts, part, (GCompareFunc)cmp_sms_part_sequence)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Cannot take part, sequence %u already taken", mm_sms_part_get_concat_sequence (part)); return FALSE; } if (mm_sms_part_get_concat_sequence (part) > self->priv->max_parts) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Cannot take part with sequence %u, maximum is %u", mm_sms_part_get_concat_sequence (part), self->priv->max_parts); return FALSE; } /* Insert sorted by concat sequence */ self->priv->parts = g_list_insert_sorted (self->priv->parts, part, (GCompareFunc)cmp_sms_part_sequence); /* We only populate contents when the multipart SMS is complete */ if (mm_sms_multipart_is_complete (self)) { GError *inner_error = NULL; if (!assemble_sms (self, &inner_error)) { /* We DO NOT propagate the error. The part was properly taken * so ownership passed to the MMSms object. */ mm_warn ("Couldn't assemble SMS: '%s'", inner_error->message); g_error_free (inner_error); } else { /* Completed AND assembled */ MMSmsState state = MM_SMS_STATE_UNKNOWN; /* Change state RECEIVING->RECEIVED, and signal completeness */ g_object_get (self, "state", &state, NULL); if (state == MM_SMS_STATE_RECEIVING) { g_object_set (self, "state", MM_SMS_STATE_RECEIVED, NULL); } } } return TRUE; } MMSms * mm_sms_new (MMBaseModem *modem) { return MM_SMS (g_object_new (MM_TYPE_SMS, MM_SMS_MODEM, modem, NULL)); } MMSms * mm_sms_singlepart_new (MMBaseModem *modem, MMSmsState state, MMSmsStorage storage, MMSmsPart *part, GError **error) { MMSms *self; self = mm_sms_new (modem); g_object_set (self, "state", state, "storage", storage, NULL); /* Keep the single part in the list */ self->priv->parts = g_list_prepend (self->priv->parts, part); if (!assemble_sms (self, error)) g_clear_object (&self); else /* Only export once properly created */ mm_sms_export (self); return self; } MMSms * mm_sms_multipart_new (MMBaseModem *modem, MMSmsState state, MMSmsStorage storage, guint reference, guint max_parts, MMSmsPart *first_part, GError **error) { MMSms *self; /* If this is the first part of a RECEIVED SMS, we overwrite the state * as RECEIVING, to indicate that it is not completed yet. */ if (state == MM_SMS_STATE_RECEIVED) state = MM_SMS_STATE_RECEIVING; self = mm_sms_new (modem); g_object_set (self, MM_SMS_IS_MULTIPART, TRUE, MM_SMS_MAX_PARTS, max_parts, MM_SMS_MULTIPART_REFERENCE, reference, "state", state, "storage", storage, NULL); if (!mm_sms_multipart_take_part (self, first_part, error)) g_clear_object (&self); return self; } MMSms * mm_sms_new_from_properties (MMBaseModem *modem, MMSmsProperties *properties, GError **error) { MMSmsPart *part; /* Don't create SMS from properties if either text or number is missing */ if (!mm_sms_properties_get_text (properties) || !mm_sms_properties_get_number (properties)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Cannot create SMS: mandatory parameter '%s' is missing", (mm_sms_properties_get_text (properties) == NULL ? "text" : "number")); } part = mm_sms_part_new (SMS_PART_INVALID_INDEX); mm_sms_part_set_text (part, mm_sms_properties_get_text (properties)); mm_sms_part_set_number (part, mm_sms_properties_get_number (properties)); mm_sms_part_set_smsc (part, mm_sms_properties_get_smsc (properties)); mm_sms_part_set_validity (part, mm_sms_properties_get_validity (properties)); mm_sms_part_set_class (part, mm_sms_properties_get_class (properties)); return mm_sms_singlepart_new (modem, MM_SMS_STATE_UNKNOWN, MM_SMS_STORAGE_UNKNOWN, /* not stored anywhere yet */ part, error); } /*****************************************************************************/ static void set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { MMSms *self = MM_SMS (object); switch (prop_id) { case PROP_PATH: g_free (self->priv->path); self->priv->path = g_value_dup_string (value); /* Export when we get a DBus connection AND we have a path */ if (!self->priv->path) mm_sms_dbus_unexport (self); else if (self->priv->connection) mm_sms_dbus_export (self); break; case PROP_CONNECTION: g_clear_object (&self->priv->connection); self->priv->connection = g_value_dup_object (value); /* Export when we get a DBus connection AND we have a path */ if (!self->priv->connection) mm_sms_dbus_unexport (self); else if (self->priv->path) mm_sms_dbus_export (self); break; case PROP_MODEM: g_clear_object (&self->priv->modem); self->priv->modem = g_value_dup_object (value); if (self->priv->modem) { /* Bind the modem's connection (which is set when it is exported, * and unset when unexported) to the SMS's connection */ g_object_bind_property (self->priv->modem, MM_BASE_MODEM_CONNECTION, self, MM_SMS_CONNECTION, G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); } break; case PROP_IS_MULTIPART: self->priv->is_multipart = g_value_get_boolean (value); break; case PROP_MAX_PARTS: self->priv->max_parts = g_value_get_uint (value); break; case PROP_MULTIPART_REFERENCE: self->priv->multipart_reference = g_value_get_uint (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MMSms *self = MM_SMS (object); switch (prop_id) { case PROP_PATH: g_value_set_string (value, self->priv->path); break; case PROP_CONNECTION: g_value_set_object (value, self->priv->connection); break; case PROP_MODEM: g_value_set_object (value, self->priv->modem); break; case PROP_IS_MULTIPART: g_value_set_boolean (value, self->priv->is_multipart); break; case PROP_MAX_PARTS: g_value_set_uint (value, self->priv->max_parts); break; case PROP_MULTIPART_REFERENCE: g_value_set_uint (value, self->priv->multipart_reference); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void mm_sms_init (MMSms *self) { /* Initialize private data */ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), MM_TYPE_SMS, MMSmsPrivate); /* Defaults */ self->priv->max_parts = 1; } static void finalize (GObject *object) { MMSms *self = MM_SMS (object); g_list_free_full (self->priv->parts, (GDestroyNotify)mm_sms_part_free); g_free (self->priv->path); G_OBJECT_CLASS (mm_sms_parent_class)->finalize (object); } static void dispose (GObject *object) { MMSms *self = MM_SMS (object); if (self->priv->connection) { /* If we arrived here with a valid connection, make sure we unexport * the object */ mm_sms_dbus_unexport (self); g_clear_object (&self->priv->connection); } g_clear_object (&self->priv->modem); G_OBJECT_CLASS (mm_sms_parent_class)->dispose (object); } static void mm_sms_class_init (MMSmsClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); g_type_class_add_private (object_class, sizeof (MMSmsPrivate)); /* Virtual methods */ object_class->get_property = get_property; object_class->set_property = set_property; object_class->finalize = finalize; object_class->dispose = dispose; klass->store = sms_store; klass->store_finish = sms_store_finish; klass->send = sms_send; klass->send_finish = sms_send_finish; klass->delete = sms_delete; klass->delete_finish = sms_delete_finish; properties[PROP_CONNECTION] = g_param_spec_object (MM_SMS_CONNECTION, "Connection", "GDBus connection to the system bus.", G_TYPE_DBUS_CONNECTION, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_CONNECTION, properties[PROP_CONNECTION]); properties[PROP_PATH] = g_param_spec_string (MM_SMS_PATH, "Path", "DBus path of the SMS", NULL, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_PATH, properties[PROP_PATH]); properties[PROP_MODEM] = g_param_spec_object (MM_SMS_MODEM, "Modem", "The Modem which owns this SMS", MM_TYPE_BASE_MODEM, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_MODEM, properties[PROP_MODEM]); properties[PROP_IS_MULTIPART] = g_param_spec_boolean (MM_SMS_IS_MULTIPART, "Is multipart", "Flag specifying if the SMS is multipart", FALSE, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_IS_MULTIPART, properties[PROP_IS_MULTIPART]); properties[PROP_MAX_PARTS] = g_param_spec_uint (MM_SMS_MAX_PARTS, "Max parts", "Maximum number of parts composing this SMS", 1,255, 1, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_MAX_PARTS, properties[PROP_MAX_PARTS]); properties[PROP_MULTIPART_REFERENCE] = g_param_spec_uint (MM_SMS_MULTIPART_REFERENCE, "Multipart reference", "Common reference for all parts in the multipart SMS", 0, G_MAXUINT, 0, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_MULTIPART_REFERENCE, properties[PROP_MULTIPART_REFERENCE]); }