diff options
-rw-r--r-- | src/mm-bearer-3gpp.c | 54 | ||||
-rw-r--r-- | src/mm-bearer.c | 228 | ||||
-rw-r--r-- | src/mm-bearer.h | 1 |
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, |