aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mm-bearer-3gpp.c54
-rw-r--r--src/mm-bearer.c228
-rw-r--r--src/mm-bearer.h1
3 files changed, 256 insertions, 27 deletions
diff --git a/src/mm-bearer-3gpp.c b/src/mm-bearer-3gpp.c
index 444f36e1..024726ba 100644
--- a/src/mm-bearer-3gpp.c
+++ b/src/mm-bearer-3gpp.c
@@ -78,6 +78,7 @@ typedef struct {
guint max_cid;
GSimpleAsyncResult *result;
GError *error;
+ GCancellable *cancellable;
} ConnectContext;
static void
@@ -121,6 +122,7 @@ connect_context_complete_and_free (ConnectContext *ctx)
g_simple_async_result_complete_in_idle (ctx->result);
+ g_object_unref (ctx->cancellable);
g_object_unref (ctx->data);
g_object_unref (ctx->primary);
g_object_unref (ctx->bearer);
@@ -130,6 +132,20 @@ connect_context_complete_and_free (ConnectContext *ctx)
}
static gboolean
+connect_context_set_error_if_cancelled (ConnectContext *ctx,
+ GError **error)
+{
+ if (!g_cancellable_is_cancelled (ctx->cancellable))
+ return FALSE;
+
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_CANCELLED,
+ "Connection setup operation has been cancelled");
+ return TRUE;
+}
+
+static gboolean
connect_finish (MMBearer *self,
GAsyncResult *res,
GError **error)
@@ -144,6 +160,12 @@ connect_report_ready (MMBaseModem *modem,
{
const gchar *result;
+ /* If cancelled, complete */
+ if (connect_context_set_error_if_cancelled (ctx, &ctx->error)) {
+ connect_context_complete_and_free (ctx);
+ return;
+ }
+
result = mm_base_modem_at_command_finish (modem, res, NULL);
if (result &&
g_str_has_prefix (result, "+CEER: ") &&
@@ -166,6 +188,9 @@ connect_ready (MMBaseModem *modem,
GAsyncResult *res,
ConnectContext *ctx)
{
+ /* DO NOT check for cancellable here. If we got here without errors, the
+ * bearer is really connected and therefore we need to reflect that in
+ * the state machine. */
mm_base_modem_at_command_finish (modem, res, &(ctx->error));
if (ctx->error) {
/* Try to get more information why it failed */
@@ -192,6 +217,12 @@ initialize_pdp_context_ready (MMBaseModem *self,
{
gchar *command;
+ /* If cancelled, complete */
+ if (connect_context_set_error_if_cancelled (ctx, &ctx->error)) {
+ connect_context_complete_and_free (ctx);
+ return;
+ }
+
mm_base_modem_at_command_finish (self, res, &(ctx->error));
if (ctx->error) {
mm_warn ("Couldn't initialize PDP context with our APN: '%s'", ctx->error->message);
@@ -230,6 +261,14 @@ find_cid_ready (MMBaseModem *self,
return;
}
+ /* If cancelled, complete. Normally, we would get the cancellation error
+ * already when finishing the sequence, but we may still get cancelled
+ * between last command result parsing in the sequence and the ready(). */
+ if (connect_context_set_error_if_cancelled (ctx, &ctx->error)) {
+ connect_context_complete_and_free (ctx);
+ return;
+ }
+
/* Initialize PDP context with our APN */
ctx->cid = g_variant_get_uint32 (result);
command = g_strdup_printf ("+CGDCONT=%u,\"IP\",\"%s\"",
@@ -261,6 +300,10 @@ parse_cid_range (MMBaseModem *self,
GMatchInfo *match_info;
guint cid = 0;
+ /* If cancelled, set result error */
+ if (connect_context_set_error_if_cancelled (ctx, result_error))
+ return FALSE;
+
if (error) {
mm_dbg ("Unexpected +CGDCONT error: '%s'", error->message);
mm_dbg ("Defaulting to CID=1");
@@ -341,6 +384,10 @@ parse_pdp_list (MMBaseModem *self,
GList *l;
guint cid;
+ /* If cancelled, set result error */
+ if (connect_context_set_error_if_cancelled (ctx, result_error))
+ return FALSE;
+
ctx->max_cid = 0;
/* Some Android phones don't support querying existing PDP contexts,
@@ -408,6 +455,7 @@ static const MMBaseModemAtCommand find_cid_sequence[] = {
static void
connect (MMBearer *self,
const gchar *number,
+ GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
@@ -489,6 +537,12 @@ connect (MMBearer *self,
ctx->data = g_object_ref (data);
ctx->bearer = g_object_ref (self);
ctx->modem = modem;
+
+ /* NOTE:
+ * We don't currently support cancelling AT commands, so we'll just check
+ * whether the operation is to be cancelled at each step. */
+ ctx->cancellable = g_object_ref (cancellable);
+
ctx->result = g_simple_async_result_new (G_OBJECT (self),
callback,
user_data,
diff --git a/src/mm-bearer.c b/src/mm-bearer.c
index 33c3a389..bede692e 100644
--- a/src/mm-bearer.c
+++ b/src/mm-bearer.c
@@ -59,30 +59,85 @@ struct _MMBearerPrivate {
gboolean connection_allowed;
/* Status of this bearer */
MMBearerStatus status;
+
+ /* Cancellable for connect() */
+ GCancellable *connect_cancellable;
+ /* handler id for the disconnect + cancel connect request */
+ gulong disconnect_signal_handler;
};
/*****************************************************************************/
/* CONNECT */
static void
+disconnect_after_cancel_ready (MMBearer *self,
+ GAsyncResult *res)
+{
+ GError *error = NULL;
+
+ if (!MM_BEARER_GET_CLASS (self)->disconnect_finish (self, res, &error)) {
+ mm_warn ("Error disconnecting bearer '%s': '%s'. "
+ "Will assume disconnected anyway.",
+ self->priv->path,
+ error->message);
+ g_error_free (error);
+ }
+ else
+ mm_dbg ("Disconnected bearer '%s'", self->priv->path);
+
+ self->priv->status = MM_BEARER_STATUS_DISCONNECTED;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATUS]);
+}
+
+static void
handle_connect_ready (MMBearer *self,
GAsyncResult *res,
GDBusMethodInvocation *invocation)
{
GError *error = NULL;
+ gboolean launch_disconnect = FALSE;
+ /* NOTE: connect() implementations *MUST* handle cancellations themselves */
if (!MM_BEARER_GET_CLASS (self)->connect_finish (self, res, &error)) {
- mm_dbg ("Couldn't connect bearer '%s'", self->priv->path);
- self->priv->status = MM_BEARER_STATUS_DISCONNECTED;
+ mm_dbg ("Couldn't connect bearer '%s': '%s'",
+ self->priv->path,
+ error->message);
+ if (g_error_matches (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_CANCELLED)) {
+ /* Will launch disconnection */
+ launch_disconnect = TRUE;
+ } else {
+ self->priv->status = MM_BEARER_STATUS_DISCONNECTED;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATUS]);
+ }
g_dbus_method_invocation_take_error (invocation, error);
}
+ else if (g_cancellable_is_cancelled (self->priv->connect_cancellable)) {
+ mm_dbg ("Connected bearer '%s', but need to disconnect", self->priv->path);
+ g_dbus_method_invocation_return_error (
+ invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_CANCELLED,
+ "Bearer got connected, but had to disconnect after cancellation request");
+ launch_disconnect = TRUE;
+ }
else {
mm_dbg ("Connected bearer '%s'", self->priv->path);
self->priv->status = MM_BEARER_STATUS_CONNECTED;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATUS]);
mm_gdbus_bearer_complete_connect (MM_GDBUS_BEARER (self), invocation);
}
- g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATUS]);
+ if (launch_disconnect) {
+ self->priv->status = MM_BEARER_STATUS_DISCONNECTING;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATUS]);
+ MM_BEARER_GET_CLASS (self)->disconnect (
+ self,
+ (GAsyncReadyCallback)disconnect_after_cancel_ready,
+ NULL);
+ }
+
g_object_unref (invocation);
}
@@ -91,6 +146,10 @@ handle_connect (MMBearer *self,
GDBusMethodInvocation *invocation,
const gchar *number)
{
+ g_assert (MM_BEARER_GET_CLASS (self)->connect != NULL);
+ g_assert (MM_BEARER_GET_CLASS (self)->connect_finish != NULL);
+
+ /* Bearer may not be allowed to connect yet */
if (!self->priv->connection_allowed) {
g_dbus_method_invocation_return_error (
invocation,
@@ -100,21 +159,45 @@ handle_connect (MMBearer *self,
return TRUE;
}
- if (MM_BEARER_GET_CLASS (self)->connect != NULL &&
- MM_BEARER_GET_CLASS (self)->connect_finish != NULL) {
- /* Connecting! */
- mm_dbg ("Connecting bearer '%s'", self->priv->path);
- self->priv->status = MM_BEARER_STATUS_CONNECTING;
- g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATUS]);
- MM_BEARER_GET_CLASS (self)->connect (
- self,
- number,
- (GAsyncReadyCallback)handle_connect_ready,
- g_object_ref (invocation));
+ /* If already connected, done */
+ if (self->priv->status == MM_BEARER_STATUS_CONNECTED) {
+ mm_gdbus_bearer_complete_connect (MM_GDBUS_BEARER (self), invocation);
return TRUE;
}
- return FALSE;
+ /* If already connecting, return error, don't allow a second request. */
+ if (self->priv->status == MM_BEARER_STATUS_CONNECTING) {
+ g_dbus_method_invocation_return_error (
+ invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_IN_PROGRESS,
+ "Bearer already being connected");
+ return TRUE;
+ }
+
+ /* If currently disconnecting, return error, previous operation should
+ * finish before allowing to connect again. */
+ if (self->priv->status == MM_BEARER_STATUS_DISCONNECTING) {
+ g_dbus_method_invocation_return_error (
+ invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Bearer currently being disconnected");
+ return TRUE;
+ }
+
+ /* Connecting! */
+ mm_dbg ("Connecting bearer '%s'", self->priv->path);
+ self->priv->connect_cancellable = g_cancellable_new ();
+ self->priv->status = MM_BEARER_STATUS_CONNECTING;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATUS]);
+ MM_BEARER_GET_CLASS (self)->connect (
+ self,
+ number,
+ self->priv->connect_cancellable,
+ (GAsyncReadyCallback)handle_connect_ready,
+ g_object_ref (invocation));
+ return TRUE;
}
/*****************************************************************************/
@@ -142,24 +225,75 @@ handle_disconnect_ready (MMBearer *self,
g_object_unref (invocation);
}
+static void
+status_changed_complete_disconnect (MMBearer *self,
+ GParamSpec *pspec,
+ GDBusMethodInvocation *invocation)
+{
+ /* We may get other states here before DISCONNECTED, like DISCONNECTING or
+ * even CONNECTED. */
+ if (self->priv->status != MM_BEARER_STATUS_DISCONNECTED)
+ return;
+
+ mm_dbg ("Disconnected bearer '%s' after cancelling previous connect request",
+ self->priv->path);
+ mm_gdbus_bearer_complete_disconnect (MM_GDBUS_BEARER (self), invocation);
+
+ g_signal_handler_disconnect (self,
+ self->priv->disconnect_signal_handler);
+ self->priv->disconnect_signal_handler = 0;
+}
+
static gboolean
handle_disconnect (MMBearer *self,
GDBusMethodInvocation *invocation)
{
- if (MM_BEARER_GET_CLASS (self)->disconnect != NULL &&
- MM_BEARER_GET_CLASS (self)->disconnect_finish != NULL) {
- /* Disconnecting! */
- mm_dbg ("Disconnecting bearer '%s'", self->priv->path);
- self->priv->status = MM_BEARER_STATUS_DISCONNECTING;
- g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATUS]);
- MM_BEARER_GET_CLASS (self)->disconnect (
- self,
- (GAsyncReadyCallback)handle_disconnect_ready,
- g_object_ref (invocation));
+ g_assert (MM_BEARER_GET_CLASS (self)->disconnect != NULL);
+ g_assert (MM_BEARER_GET_CLASS (self)->disconnect_finish != NULL);
+
+ /* If already disconnected, done */
+ if (self->priv->status == MM_BEARER_STATUS_DISCONNECTED) {
+ mm_gdbus_bearer_complete_disconnect (MM_GDBUS_BEARER (self), invocation);
return TRUE;
}
- return FALSE;
+ /* If already disconnecting, return error, don't allow a second request. */
+ if (self->priv->status == MM_BEARER_STATUS_DISCONNECTING) {
+ g_dbus_method_invocation_return_error (
+ invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_IN_PROGRESS,
+ "Bearer already being disconnected");
+ return TRUE;
+ }
+
+ mm_dbg ("Disconnecting bearer '%s'", self->priv->path);
+
+ /* If currently connecting, try to cancel that operation, and wait to get
+ * disconnected. */
+ if (self->priv->status == MM_BEARER_STATUS_CONNECTING) {
+ /* We MUST ensure that we get to DISCONNECTED */
+ g_cancellable_cancel (self->priv->connect_cancellable);
+ /* Note that we only allow to remove disconnected bearers, so should
+ * be safe to assume that we'll get the signal handler called properly
+ */
+ self->priv->disconnect_signal_handler =
+ g_signal_connect (self,
+ "notify::" MM_BEARER_STATUS,
+ (GCallback)status_changed_complete_disconnect,
+ g_object_ref (invocation));
+
+ return TRUE;
+ }
+
+ /* Disconnecting! */
+ self->priv->status = MM_BEARER_STATUS_DISCONNECTING;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATUS]);
+ MM_BEARER_GET_CLASS (self)->disconnect (
+ self,
+ (GAsyncReadyCallback)handle_disconnect_ready,
+ g_object_ref (invocation));
+ return TRUE;
}
/*****************************************************************************/
@@ -227,6 +361,26 @@ mm_bearer_set_connection_allowed (MMBearer *self)
self->priv->connection_allowed = TRUE;
}
+static void
+disconnect_after_forbidden_ready (MMBearer *self,
+ GAsyncResult *res)
+{
+ GError *error = NULL;
+
+ if (!MM_BEARER_GET_CLASS (self)->disconnect_finish (self, res, &error)) {
+ mm_warn ("Error disconnecting bearer '%s': '%s'. "
+ "Will assume disconnected anyway.",
+ self->priv->path,
+ error->message);
+ g_error_free (error);
+ }
+ else
+ mm_dbg ("Disconnected bearer '%s'", self->priv->path);
+
+ self->priv->status = MM_BEARER_STATUS_DISCONNECTED;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATUS]);
+}
+
void
mm_bearer_set_connection_forbidden (MMBearer *self)
{
@@ -235,7 +389,27 @@ mm_bearer_set_connection_forbidden (MMBearer *self)
mm_dbg ("Connection in bearer '%s' is forbidden", self->priv->path);
self->priv->connection_allowed = FALSE;
- /* TODO: possibly, force disconnection */
+
+ if (self->priv->status == MM_BEARER_STATUS_DISCONNECTING ||
+ self->priv->status == MM_BEARER_STATUS_DISCONNECTED) {
+ return;
+ }
+
+ mm_dbg ("Disconnecting bearer '%s'", self->priv->path);
+
+ /* If currently connecting, try to cancel that operation. */
+ if (self->priv->status == MM_BEARER_STATUS_CONNECTING) {
+ g_cancellable_cancel (self->priv->connect_cancellable);
+ return;
+ }
+
+ /* Disconnecting! */
+ self->priv->status = MM_BEARER_STATUS_DISCONNECTING;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATUS]);
+ MM_BEARER_GET_CLASS (self)->disconnect (
+ self,
+ (GAsyncReadyCallback)disconnect_after_forbidden_ready,
+ NULL);
}
void
diff --git a/src/mm-bearer.h b/src/mm-bearer.h
index 7267dead..e619bf1a 100644
--- a/src/mm-bearer.h
+++ b/src/mm-bearer.h
@@ -62,6 +62,7 @@ struct _MMBearerClass {
/* Connect this bearer */
void (* connect) (MMBearer *bearer,
const gchar *number,
+ GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean (* connect_finish) (MMBearer *bearer,