| /* -*- 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 <config.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <ctype.h> |
| |
| #include <ModemManager.h> |
| #include <libmm-common.h> |
| |
| #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=<length>[, <stat>]<CR> PDU can be entered. <CTRL-Z>/<ESC> */ |
| |
| 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]); |
| } |