Logo Search packages:      
Sourcecode: telepathy-ring version File versions  Download package

sms-service.c

/*
 * modem/sms-service.c - ModemSMSService class
 *
 * Copyright (C) 2008-2010 Nokia Corporation
 *   @author Pekka Pessi <first.surname@nokia.com>
 *   @author Lassi Syrjala <first.surname@nokia.com>
 *
 * 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 MODEM_DEBUG_FLAG MODEM_SERVICE_SMS

#include "debug.h"

#include "modem/sms.h"
#include "modem/request-private.h"
#include "modem/errors.h"

#include "sms-glib/deliver.h"
#include "sms-glib/status-report.h"

#include "modem/ofono.h"

#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>

#include "signals-marshal.h"

#include <uuid/uuid.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

/* ---------------------------------------------------------------------- */

G_DEFINE_TYPE(ModemSMSService, modem_sms_service, G_TYPE_OBJECT)

/* Signals we emit */
enum {
  SIGNAL_CONNECTED,
  SIGNAL_DELIVER,
  SIGNAL_OUTGOING_COMPLETE,
  SIGNAL_OUTGOING_ERROR,
  SIGNAL_STATUS_REPORT,
  N_SIGNALS
};

static guint signals[N_SIGNALS] = {0};

/* Properties */
enum {
  PROP_NONE,
  PROP_CONTENT_TYPES,
  PROP_SMSC,
  PROP_VALIDITY_PERIOD,
  PROP_REDUCED_CHARSET,
  LAST_PROPERTY
};

/* private data */
00077 struct _ModemSMSServicePrivate
{
  char *client_id;
  char *bus_name;

  time_t connected;             /* Timestamp when got connected */

  char *smsc;
  guint validity_period;

  DBusGProxy *proxy;

  struct {
    GQueue queue[1];
    GError *error;
    int read;
  } connecting;

  char **content_types;

  GHashTable *received;

  unsigned reduced_charset:1;
  unsigned loopback:1;
  unsigned dispose_has_run:1;
  unsigned :0;
};

/* ------------------------------------------------------------------------ */

static ModemOfonoPropChangedCb on_manager_property_changed;
static ModemOfonoPropsReply reply_to_sms_manager_get_properties;

static void modem_sms_check_connected(ModemSMSService *self,
  ModemRequest *request,
  char const *operation,
  const GError *error);

static void modem_sms_incoming_deliver(ModemSMSService *self,
  SMSGDeliver *deliver);

/* ------------------------------------------------------------------------ */
/* GObject interface */

static void
modem_sms_service_init(ModemSMSService *self)
{
  DEBUG("enter");

  self->priv = G_TYPE_INSTANCE_GET_PRIVATE(
    self, MODEM_TYPE_SMS_SERVICE, ModemSMSServicePrivate);

  g_queue_init(self->priv->connecting.queue);

  self->priv->received = g_hash_table_new_full(g_str_hash, g_str_equal,
                         NULL, /* Message object stored in hash owns the key ref, too */
                         g_object_unref);
}

static void
modem_sms_service_get_property(GObject *object,
  guint property_id,
  GValue *value,
  GParamSpec *pspec)
{
  ModemSMSService *self = MODEM_SMS_SERVICE(object);
  ModemSMSServicePrivate *priv = self->priv;

  switch(property_id) {

    case PROP_CONTENT_TYPES:
      g_value_set_boxed(value, priv->content_types);
      break;

    case PROP_SMSC:
      g_value_set_string(value, priv->smsc);
      break;

    case PROP_VALIDITY_PERIOD:
      g_value_set_uint(value, priv->validity_period);
      break;

    case PROP_REDUCED_CHARSET:
      g_value_set_boolean(value, priv->reduced_charset);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
      break;
  }
}

static void
modem_sms_service_set_property(GObject *object,
  guint property_id,
  const GValue *value,
  GParamSpec *pspec)
{
  ModemSMSService *self = MODEM_SMS_SERVICE(object);
  ModemSMSServicePrivate *priv = self->priv;
  gpointer old;

  switch(property_id) {

    case PROP_CONTENT_TYPES:
      old = priv->content_types;
      priv->content_types = g_value_dup_boxed(value);
      if (old) g_boxed_free(G_TYPE_STRV, old);
      break;

    case PROP_SMSC:
      old = priv->smsc;
      priv->smsc = g_value_dup_string(value);
      g_free(old);
      break;

    case PROP_VALIDITY_PERIOD:
      priv->validity_period = g_value_get_uint(value);
      break;

    case PROP_REDUCED_CHARSET:
      priv->reduced_charset = g_value_get_boolean(value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
      break;
  }
}

static void
modem_sms_service_constructed(GObject *object)
{
  if (G_OBJECT_CLASS(modem_sms_service_parent_class)->constructed)
    G_OBJECT_CLASS(modem_sms_service_parent_class)->constructed(object);
}

static void
modem_sms_service_dispose(GObject *object)
{
  ModemSMSService *self = MODEM_SMS_SERVICE(object);
  ModemSMSServicePrivate *priv = self->priv;

  if (priv->dispose_has_run)
    return;
  priv->dispose_has_run = TRUE;

  modem_sms_service_disconnect(self);

  if (priv->proxy)
    g_object_run_dispose(G_OBJECT(priv->proxy));

  if (G_OBJECT_CLASS(modem_sms_service_parent_class)->dispose)
    G_OBJECT_CLASS(modem_sms_service_parent_class)->dispose(object);
}

static void
modem_sms_service_finalize(GObject *object)
{
  ModemSMSService *self = MODEM_SMS_SERVICE(object);
  ModemSMSServicePrivate *priv = self->priv;

  DEBUG("enter");

  /* Free any data held directly by the object here */
  g_free(priv->client_id);
  g_free(priv->bus_name);
  g_free(priv->smsc);

  if (priv->proxy)
    g_object_unref(priv->proxy);
  if (priv->connecting.error)
    g_clear_error(&priv->connecting.error);

  if (priv->content_types)
    g_boxed_free(G_TYPE_STRV, priv->content_types);

  if (priv->received)
    g_hash_table_destroy(priv->received);

  G_OBJECT_CLASS(modem_sms_service_parent_class)->finalize(object);
}


static void
modem_sms_service_class_init(ModemSMSServiceClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS(klass);

  DEBUG("enter");

  object_class->get_property = modem_sms_service_get_property;
  object_class->set_property = modem_sms_service_set_property;
  object_class->constructed = modem_sms_service_constructed;
  object_class->dispose = modem_sms_service_dispose;
  object_class->finalize = modem_sms_service_finalize;

  g_object_class_install_property(
    object_class, PROP_CONTENT_TYPES,
    g_param_spec_boxed("content-types",
      "Content types used",
      "List of MIME content types used by application.",
      G_TYPE_STRV,
      G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
      G_PARAM_STATIC_STRINGS));

  g_object_class_install_property(
    object_class, PROP_SMSC,
    g_param_spec_string("service-centre",
      "SMS Service Centre",
      "ISDN Address for SMS Service Centre",
      "", /* default value */
      G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
      G_PARAM_STATIC_STRINGS));

  g_object_class_install_property(
    object_class, PROP_VALIDITY_PERIOD,
    g_param_spec_uint("validity-period",
      "SMS Validity Period",
      "Period while SMS service centre "
      "keep trying to deliver SMS.",
      /* anything above 0 gets rounded up to 5 minutes */
      0,  /* 0 means no validity period */
      63 * 7 * 24 * 60 * 60, /* max - 63 weeks */
      0, /* no validity period - it is up to service centre */
      G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
      G_PARAM_STATIC_STRINGS));

  g_object_class_install_property(
    object_class, PROP_REDUCED_CHARSET,
    g_param_spec_boolean("reduced-charset",
      "SMS reduced character set support",
      "Whether SMS should be encoded with "
      "a reduced character set",
      FALSE,
      G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
      G_PARAM_STATIC_STRINGS));

  signals[SIGNAL_CONNECTED] =
    g_signal_new("connected",
      G_OBJECT_CLASS_TYPE (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
      0,
      NULL, NULL,
      g_cclosure_marshal_VOID__VOID,
      G_TYPE_NONE, 0);

  signals[SIGNAL_DELIVER] =
    g_signal_new("deliver",
      G_OBJECT_CLASS_TYPE (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
      0,
      NULL, NULL,
      g_cclosure_marshal_VOID__OBJECT,
      G_TYPE_NONE, 1,
      G_TYPE_OBJECT);

  signals[SIGNAL_OUTGOING_COMPLETE] =
    g_signal_new("outgoing-complete",
      G_OBJECT_CLASS_TYPE (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
      0,
      NULL, NULL,
      _modem__marshal_VOID__STRING_STRING,
      G_TYPE_NONE, 2,
      G_TYPE_STRING, G_TYPE_STRING);

  signals[SIGNAL_OUTGOING_ERROR] =
    g_signal_new("outgoing-error",
      G_OBJECT_CLASS_TYPE (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
      0,
      NULL, NULL,
      _modem__marshal_VOID__STRING_STRING_POINTER,
      G_TYPE_NONE, 3,
      G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);

  signals[SIGNAL_STATUS_REPORT] =
    g_signal_new("state-report",
      G_OBJECT_CLASS_TYPE (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
      0,
      NULL, NULL,
      g_cclosure_marshal_VOID__OBJECT,
      G_TYPE_NONE, 1,
      G_TYPE_OBJECT);

  g_type_class_add_private(klass, sizeof (ModemSMSServicePrivate));
}

/* ---------------------------------------------------------------------- */
/* Properties */
static void
on_manager_property_changed(DBusGProxy *proxy,
  char const *property,
  GValue const *value,
  gpointer user_data)
{
  char *s;

  s = g_strdup_value_contents(value);
  DEBUG("%s = %s", property, s);
  g_free(s);
}

static void
reply_to_sms_manager_get_properties(gpointer _self,
  ModemRequest *request,
  GHashTable *properties,
  GError const *error,
  gpointer user_data)
{
  ModemSMSService *self = MODEM_SMS_SERVICE(_self);

  DEBUG("enter");

  if (!error) {
    char *key;
    GValue *value;
    GHashTableIter iter[1];

    g_hash_table_iter_init(iter, properties);
    while (g_hash_table_iter_next(iter,
        (gpointer)&key, (gpointer)&value)) {

      char *s = g_strdup_value_contents(value);
      DEBUG("%s = %s", key, s);
      g_free(s);
    }
  }

  modem_sms_check_connected(self, request, "GetProperties", error);
}

/* ---------------------------------------------------------------------- */
/* Signal connection helpers */

gulong
modem_sms_connect_to_connected(ModemSMSService *self,
  ModemSMSConnectedHandler *user_function,
  gpointer user_data)
{
  return g_signal_connect(self, "connected", G_CALLBACK(user_function), user_data);
}

gulong
modem_sms_connect_to_deliver(ModemSMSService *self,
  ModemSMSDeliverHandler *user_function,
  gpointer user_data)
{
  return g_signal_connect(self, "deliver", G_CALLBACK(user_function), user_data);
}

/* --------------------------------------------------------------------------------- */
/* modem_sms_service interface */


gboolean
modem_sms_service_is_connected(ModemSMSService const *self)
{
  return MODEM_IS_SMS_SERVICE(self) &&
    self->priv->connected != 0;
}

gboolean
modem_sms_service_is_connecting(ModemSMSService const *self)
{
  return MODEM_IS_SMS_SERVICE(self) &&
    !g_queue_is_empty(self->priv->connecting.queue);
}

guint64
modem_sms_service_time_connected(ModemSMSService const *self)
{
  if (MODEM_IS_SMS_SERVICE(self))
    return (guint64)self->priv->connected;
  else
    return 0;
}

/* ------------------------------------------------------------------------- */

/* FIXME: something for Ofono... */
static char *
modem_sms_generate_token(void)
{
  char *token;
  uuid_t uu;

  token = g_new(gchar, 37);
  uuid_generate_random(uu);
  uuid_unparse_lower(uu, token);

  return token;
}

static void
dump_message_dict(GHashTable *dict)
{
  char *key;
  GValue *value;
  GHashTableIter iter[1];

  g_hash_table_iter_init(iter, dict);
  while (g_hash_table_iter_next(iter, (gpointer)&key, (gpointer)&value)) {
    char *s = g_strdup_value_contents(value);
    DEBUG("%s = %s", key, s);
    g_free(s);
  }
}

static void
on_incoming_message(DBusGProxy *proxy,
  char const *message,
  GHashTable *dict,
  gpointer _self)
{
  /* FIXME: ofono does not provide this */
  char const *smsc = "1234567";
  char const *type = "text/plain";
  char const *mwi_type = "";
  char const *originator = "";

  char *token;
  GError *error = NULL;
  GValue *value;
  SMSGDeliver *d;
  ModemSMSService *self = MODEM_SMS_SERVICE(_self);

  DEBUG("message = \"%s\"", message);

  dump_message_dict(dict);

  value = g_hash_table_lookup(dict, "Sender");
  if (value)
    originator = g_value_get_string(value);

  token = modem_sms_generate_token();
  d = sms_g_deliver_incoming(message, token, originator,
      smsc, type, mwi_type, &error);

  if (!d) {
    modem_message(MODEM_SERVICE_SMS,
      "deserializing SMS-DELIVER \"%s\" failed: "
      GERROR_MSG_FMT, token, GERROR_MSG_CODE(error));
    g_clear_error(&error);
  }
  else {
    modem_sms_incoming_deliver(self, d);
    g_object_unref(d);
  }

  g_free(token);
}

/* ------------------------------------------------------------------------- */

/* Connect to SMS */
gboolean
modem_sms_service_connect(ModemSMSService *self, char const *object_path)
{
  DBusGProxy *p;
  ModemRequest *request;

  DEBUG("enter");

  ModemSMSServicePrivate *priv = self->priv;

  if (priv->dispose_has_run)
    return FALSE;
  if (modem_sms_service_is_connected(self))
    return TRUE;
  if (modem_sms_service_is_connecting(self))
    return TRUE;

  if (!priv->proxy)
    priv->proxy = modem_ofono_proxy(object_path, OFONO_IFACE_SMS);
  if (!priv->proxy)
    g_error("Failed to proxy the SMS service");

  p = priv->proxy;
  modem_ofono_proxy_connect_to_property_changed(
    p, on_manager_property_changed, self);

#define CONNECT(handler, name, signature...)                            \
  dbus_g_proxy_add_signal(p, (name), ##signature);                      \
  dbus_g_proxy_connect_signal(p, (name), G_CALLBACK(handler), self, NULL)

  /* XXX: SMS class 0. Does Ofono actually need a separate
   * signal for them instead of providing the class in the
   * properties dict? */
  CONNECT(on_incoming_message, "ImmediateMessage",
    G_TYPE_STRING, MODEM_TYPE_DBUS_DICT, G_TYPE_INVALID);
  CONNECT(on_incoming_message, "IncomingMessage",
    G_TYPE_STRING, MODEM_TYPE_DBUS_DICT, G_TYPE_INVALID);

  request = modem_ofono_proxy_request_properties(
    p, reply_to_sms_manager_get_properties, self, NULL);

  g_queue_push_tail(priv->connecting.queue, request);

  return TRUE;
}

GError *
modem_sms_service_connection_error(ModemSMSService *self)
{
  return MODEM_SMS_SERVICE(self)
    && self->priv->connecting.error
    ? g_error_copy(self->priv->connecting.error)
    : NULL;
}

gboolean
modem_sms_service_is_error_serious(GError *error)
{
  if (error == NULL)
    return FALSE;

  return TRUE;
}

static void
modem_sms_check_connected(ModemSMSService *self,
  ModemRequest *request,
  char const *operation,
  const GError *error)
{
  ModemSMSServicePrivate *priv = self->priv;

  if (g_queue_find(priv->connecting.queue, request)) {
    g_queue_remove(priv->connecting.queue, request);

    if (error) {
      if (!modem_sms_service_is_error_serious(priv->connecting.error)) {
        g_clear_error(&priv->connecting.error);
        g_set_error(&priv->connecting.error,
          error->domain, error->code,
          "%s.%s(): %s", OFONO_IFACE_SMS, operation, error->message);
      }

      modem_critical(MODEM_SERVICE_SMS, GERROR_MSG_FMT,
        GERROR_MSG_CODE(priv->connecting.error));
    }

    if (g_queue_is_empty(priv->connecting.queue)) {
      if (priv->connecting.error) {
        priv->connected = 0;
      }
      else {
        GHashTableIter i[1];
        time_t now = time(NULL);
        gpointer key, value;

        for (g_hash_table_iter_init(i, priv->received);
             g_hash_table_iter_next(i, &key, &value);) {
          g_object_set(value, "time-delivered", (guint64)now, NULL);
        }

        priv->connected = now;
      }

      g_signal_emit(self, signals[SIGNAL_CONNECTED], 0);
    }
  }
}

void
modem_sms_service_disconnect(ModemSMSService *self)
{
  ModemSMSServicePrivate *priv = self->priv;

  DEBUG("enter");

  priv->connected = 0;

  while (!g_queue_is_empty(priv->connecting.queue))
    modem_request_cancel(g_queue_pop_head(priv->connecting.queue));

  g_hash_table_remove_all(priv->received);
}

/* ---------------------------------------------------------------------- */
/* Message deliver */

static void
modem_sms_incoming_deliver(ModemSMSService *self, SMSGDeliver *deliver)
{
  ModemSMSServicePrivate *priv = self->priv;
  gchar const *content_type = sms_g_deliver_get_content_type(deliver);
  gchar const *token = sms_g_deliver_get_message_token(deliver);

  if (priv->content_types) {
    int i;

    for (i = 0; priv->content_types[i]; i++) {
      if (g_ascii_strcasecmp(content_type, priv->content_types[i]) == 0)
        break;
    }

    if (priv->content_types[i] == NULL) {
      DEBUG("SMS-DELIVER containing %s is ignored", content_type);
      return;
    }
  }

  if (g_hash_table_lookup(priv->received, (gpointer)token)) {
    DEBUG("SMS-DELIVER %s already read, ignoring", token);
    return;
  }

  g_hash_table_insert(priv->received, (gpointer)token, g_object_ref(deliver));

  DEBUG("SMS-DELIVER connected:%ld", priv->connected);

  if (priv->connected)
    g_signal_emit(self, signals[SIGNAL_DELIVER], 0, deliver);
}

static void
modem_sms_incoming_status_report(ModemSMSService *self,
  SMSGStatusReport *sr)
{
  ModemSMSServicePrivate *priv = self->priv;
  gchar const *token = sms_g_status_report_get_message_token(sr);

  if (g_hash_table_lookup(priv->received, (gpointer)token)) {
    DEBUG("SMS-STATUS REPORT %s already read, ignoring", token);
    return;
  }
  g_hash_table_insert(priv->received, (gpointer)token, g_object_ref(sr));

  if (priv->connected)
    g_signal_emit(self, signals[SIGNAL_STATUS_REPORT], 0, sr);
}

/* ---------------------------------------------------------------------- */
/* Sending */

static void
reply_to_send_message(DBusGProxy *proxy,
  DBusGProxyCall *call,
  void *_request)
{
  char *token = NULL;
  ModemRequest *request = _request;
  ModemSMSService *self = modem_request_object(request);
  ModemSMSServiceSendReply *callback = modem_request_callback(request);
  gpointer user_data = modem_request_user_data(request);

  GError *error = NULL;

  if (dbus_g_proxy_end_call(proxy, call, &error, G_TYPE_INVALID)) {
    char const *destination;

    destination = modem_request_get_data(request, "destination");

    token = modem_sms_generate_token();
  }

  callback(self, request, token, error, user_data);

  g_free(token);
  g_clear_error(&error);
}

ModemRequest *
modem_sms_request_send(ModemSMSService *self,
  char const *to, char const *message,
  ModemSMSServiceSendReply *reply, gpointer user_data)
{
  ModemRequest *request;
  ModemSMSServicePrivate *priv = self->priv;

  DEBUG(OFONO_IFACE_SMS ".SendMessage(%s,%s)", to, message);
  request = modem_request(self, priv->proxy,
            "SendMessage", reply_to_send_message,
            G_CALLBACK(reply), user_data,
            G_TYPE_STRING, to,
            G_TYPE_STRING, message,
            G_TYPE_INVALID);

  if (request)
    modem_request_add_data_full(request, "destination",
      g_strdup(to), g_free);

  return request;
}

/* ---------------------------------------------------------------------- */
/* Handler interface */

/* ---------------------------------------------------------------------- */
/* Error handling */

Generated by  Doxygen 1.6.0   Back to index