diff options
author | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2009-04-06 13:47:13 +0100 |
---|---|---|
committer | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2009-04-06 13:47:13 +0100 |
commit | 8a892e9274d21f6303c7ed0fd47fe29185d2b7bd (patch) | |
tree | 9cf8188465b28ea37794ecdccb08663f4ecd479d | |
parent | ed1f55a992a7884b29974682387ea9b363384d42 (diff) | |
parent | c134cd4a6feef10c7b51b6aedd44d5f9ccfd189c (diff) | |
download | telepathy-mission-control-8a892e9274d21f6303c7ed0fd47fe29185d2b7bd.tar.gz |
Merge branch 'requests'
Conflicts:
src/mcd-channel.c
Reviewed-by: Alberto Mardegan <alberto.mardegan@nokia.com>
-rw-r--r-- | src/mcd-account-priv.h | 7 | ||||
-rw-r--r-- | src/mcd-account-requests.c | 83 | ||||
-rw-r--r-- | src/mcd-account-requests.h | 9 | ||||
-rw-r--r-- | src/mcd-account.c | 16 | ||||
-rw-r--r-- | src/mcd-channel.c | 254 | ||||
-rw-r--r-- | src/mcd-channel.h | 20 | ||||
-rw-r--r-- | src/mcd-connection.c | 7 | ||||
-rw-r--r-- | src/mcd-dispatcher.c | 113 | ||||
-rw-r--r-- | src/mcd-dispatcher.h | 6 | ||||
-rw-r--r-- | test/twisted/Makefile.am | 3 | ||||
-rw-r--r-- | test/twisted/account-requests/create-text.py | 23 | ||||
-rw-r--r-- | test/twisted/account-requests/delete-account-during-request.py | 136 | ||||
-rw-r--r-- | test/twisted/dispatcher/already-has-channel.py | 235 | ||||
-rw-r--r-- | test/twisted/dispatcher/create-text.py | 184 | ||||
-rw-r--r-- | test/twisted/dispatcher/dispatch-text.py | 2 | ||||
-rw-r--r-- | test/twisted/mctest.py | 19 |
16 files changed, 998 insertions, 119 deletions
diff --git a/src/mcd-account-priv.h b/src/mcd-account-priv.h index 96e7b11f..ac669d12 100644 --- a/src/mcd-account-priv.h +++ b/src/mcd-account-priv.h @@ -47,10 +47,9 @@ void _mcd_account_connect (McdAccount *account, GHashTable *params); typedef void (*McdOnlineRequestCb) (McdAccount *account, gpointer userdata, const GError *error); -gboolean _mcd_account_online_request (McdAccount *account, - McdOnlineRequestCb callback, - gpointer userdata, - GError **imm_error); +void _mcd_account_online_request (McdAccount *account, + McdOnlineRequestCb callback, + gpointer userdata); void _mcd_account_request_connection (McdAccount *account); G_GNUC_INTERNAL void _mcd_account_online_request_completed (McdAccount *account, diff --git a/src/mcd-account-requests.c b/src/mcd-account-requests.c index 555280e4..c21bde4b 100644 --- a/src/mcd-account-requests.c +++ b/src/mcd-account-requests.c @@ -40,6 +40,7 @@ #include "mcd-account-manager.h" #include "mcd-misc.h" #include "_gen/interfaces.h" +#include "_gen/svc-request.h" static void online_request_cb (McdAccount *account, gpointer userdata, const GError *error) @@ -128,6 +129,10 @@ on_channel_status_changed (McdChannel *channel, McdChannelStatus status, _mcd_channel_get_request_path (channel), error->message); err_string = _mcd_build_error_string (error); + /* FIXME: ideally the McdChannel should emit this signal itself, and + * the Account.Interface.ChannelRequests should catch and re-emit it */ + mc_svc_channel_request_emit_failed (channel, err_string, + error->message); mc_svc_account_interface_channelrequests_emit_failed (account, _mcd_channel_get_request_path (channel), err_string, error->message); @@ -137,6 +142,9 @@ on_channel_status_changed (McdChannel *channel, McdChannelStatus status, } else if (status == MCD_CHANNEL_STATUS_DISPATCHED) { + /* FIXME: ideally the McdChannel should emit this signal itself, and + * the Account.Interface.ChannelRequests should catch and re-emit it */ + mc_svc_channel_request_emit_succeeded (channel); mc_svc_account_interface_channelrequests_emit_succeeded (account, _mcd_channel_get_request_path (channel)); @@ -144,15 +152,17 @@ on_channel_status_changed (McdChannel *channel, McdChannelStatus status, } } -static McdChannel * -create_request (McdAccount *account, GHashTable *properties, - guint64 user_time, const gchar *preferred_handler, - gboolean use_existing, GError **error) +McdChannel * +_mcd_account_create_request (McdAccount *account, GHashTable *properties, + gint64 user_time, const gchar *preferred_handler, + gboolean use_existing, gboolean proceeding, + GError **error) { McdChannel *channel; GHashTable *props; - - g_return_val_if_fail (error != NULL, NULL); + TpDBusDaemon *dbus_daemon = mcd_account_manager_get_dbus_daemon ( + mcd_account_get_account_manager (account)); + DBusGConnection *dgc = tp_proxy_get_dbus_connection (dbus_daemon); if (mcd_mission_get_flags (MCD_MISSION (mcd_master_get_default ())) & MCD_SYSTEM_MEMORY_CONSERVED) @@ -164,33 +174,23 @@ create_request (McdAccount *account, GHashTable *properties, /* We MUST deep-copy the hash-table, as we don't know how dbus-glib will * free it */ props = _mcd_deepcopy_asv (properties); - channel = mcd_channel_new_request (props, user_time, - preferred_handler); + channel = mcd_channel_new_request (account, dgc, props, user_time, + preferred_handler, use_existing, + proceeding); g_hash_table_unref (props); - _mcd_channel_set_request_use_existing (channel, use_existing); + + /* FIXME: this isn't ideal - if the account is deleted, Proceed will fail, + * whereas what we want to happen is that Proceed will succeed but + * immediately cause a failure to be signalled. It'll do for now though. */ /* we use connect_after, to make sure that other signals (such as * RemoveFailedRequest) are emitted before the Failed signal */ - g_signal_connect_after (channel, "status-changed", + /* WARNING: on_channel_status_changed unrefs the McdChannel (!), so we + * give it an extra reference, so that we can return a ref from this + * function */ + g_signal_connect_after (g_object_ref (channel), "status-changed", G_CALLBACK (on_channel_status_changed), account); - _mcd_account_online_request (account, online_request_cb, channel, error); - if (*error) - { - g_warning ("_mcd_account_online_request: %s", - (*error)->message); - mcd_channel_take_error (channel, g_error_copy (*error)); - /* no unref here, as this will invoke our handler which will - * unreference the channel */ - channel = NULL; - } - else - { - /* the channel must be kept alive until online_request_cb is called; - * this reference will be removed in that callback */ - g_object_ref (channel); - } - return channel; } @@ -198,9 +198,22 @@ const McdDBusProp account_channelrequests_properties[] = { { 0 }, }; +void +_mcd_account_proceed_with_request (McdAccount *account, + McdChannel *channel) +{ + /* Put the account online if necessary, and when that's finished, + * make the actual request. This is the equivalent of Proceed() in the + * new API. + * + * (The callback releases this reference.) */ + _mcd_account_online_request (account, online_request_cb, + g_object_ref (channel)); +} + static void account_request_common (McdAccount *account, GHashTable *properties, - guint64 user_time, const gchar *preferred_handler, + gint64 user_time, const gchar *preferred_handler, DBusGMethodInvocation *context, gboolean use_existing) { GError *error = NULL; @@ -208,14 +221,20 @@ account_request_common (McdAccount *account, GHashTable *properties, McdChannel *channel; McdDispatcher *dispatcher; - channel = create_request (account, properties, user_time, - preferred_handler, use_existing, &error); + channel = _mcd_account_create_request (account, properties, user_time, + preferred_handler, use_existing, + TRUE /* proceeding */, &error); + if (error) { + g_assert (channel == NULL); dbus_g_method_return_error (context, error); g_error_free (error); return; } + + _mcd_account_proceed_with_request (account, channel); + request_id = _mcd_channel_get_request_path (channel); DEBUG ("returning %s", request_id); if (use_existing) @@ -227,6 +246,10 @@ account_request_common (McdAccount *account, GHashTable *properties, dispatcher = mcd_master_get_dispatcher (mcd_master_get_default ()); _mcd_dispatcher_add_request (dispatcher, account, channel); + + /* we still have a ref returned by _mcd_account_create_request(), which + * is no longer necessary at this point */ + g_object_unref (channel); } static void diff --git a/src/mcd-account-requests.h b/src/mcd-account-requests.h index e6b0cdbb..2440ca00 100644 --- a/src/mcd-account-requests.h +++ b/src/mcd-account-requests.h @@ -28,12 +28,21 @@ /* auto-generated stubs */ #include "_gen/svc-Account_Interface_ChannelRequests.h" +#include "mcd-channel.h" #include "mcd-dbusprop.h" G_BEGIN_DECLS extern const McdDBusProp account_channelrequests_properties[]; +G_GNUC_INTERNAL McdChannel *_mcd_account_create_request (McdAccount *account, + GHashTable *properties, gint64 user_action_time, + const gchar *preferred_handler, gboolean use_existing, + gboolean proceeding, GError **error); + +G_GNUC_INTERNAL void _mcd_account_proceed_with_request (McdAccount *account, + McdChannel *channel); + void account_channelrequests_iface_init (McSvcAccountInterfaceChannelRequestsClass *iface, gpointer iface_data); diff --git a/src/mcd-account.c b/src/mcd-account.c index ae15e152..776a47bb 100644 --- a/src/mcd-account.c +++ b/src/mcd-account.c @@ -2301,29 +2301,26 @@ _mcd_account_request_connection (McdAccount *account) * @account: the #McdAccount. * @callback: a #McdOnlineRequestCb. * @userdata: user data to be passed to @callback. - * @imm_error: pointer to a #GError location, or %NULL. * - * If the account is online, call @callbeck immediately; else, try to put the + * If the account is online, call @callback immediately; else, try to put the * account online (set its presence to the automatic presence) and eventually * invoke @callback. * - * Returns: %TRUE if @callback was/will be invoked, %FALSE otherwise. + * @callback is always invoked exactly once. */ -gboolean +void _mcd_account_online_request (McdAccount *account, McdOnlineRequestCb callback, - gpointer userdata, - GError **imm_error) + gpointer userdata) { McdAccountPrivate *priv = account->priv; - GError *error = NULL; DEBUG ("connection status for %s is %d", priv->unique_name, priv->conn_status); if (priv->conn_status == TP_CONNECTION_STATUS_CONNECTED) { - /* invoke the callback now */ - callback (account, userdata, error); + /* invoke the callback now */ + callback (account, userdata, NULL); } else { @@ -2340,7 +2337,6 @@ _mcd_account_online_request (McdAccount *account, data->user_data = userdata; priv->online_requests = g_list_append (priv->online_requests, data); } - return TRUE; } GKeyFile * diff --git a/src/mcd-channel.c b/src/mcd-channel.c index 1ac9f7c7..38bfc2cc 100644 --- a/src/mcd-channel.c +++ b/src/mcd-channel.c @@ -38,15 +38,24 @@ #include <telepathy-glib/interfaces.h> #include <telepathy-glib/gtypes.h> #include <telepathy-glib/dbus.h> +#include <telepathy-glib/svc-generic.h> #include <telepathy-glib/util.h> +#include "mcd-account-requests.h" #include "mcd-channel.h" #include "mcd-enum-types.h" #include "_gen/gtypes.h" +#include "_gen/interfaces.h" +#include "_gen/svc-request.h" #define MCD_CHANNEL_PRIV(channel) (MCD_CHANNEL (channel)->priv) -G_DEFINE_TYPE (McdChannel, mcd_channel, MCD_TYPE_MISSION); +static void request_iface_init (gpointer, gpointer); + +G_DEFINE_TYPE_WITH_CODE (McdChannel, mcd_channel, MCD_TYPE_MISSION, + G_IMPLEMENT_INTERFACE (MC_TYPE_SVC_CHANNEL_REQUEST, request_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES, + tp_dbus_properties_mixin_iface_init)) typedef struct _McdChannelRequestData McdChannelRequestData; @@ -76,9 +85,11 @@ struct _McdChannelRequestData GHashTable *properties; guint target_handle; /* used only if the Requests interface is absent */ - guint64 user_time; + gint64 user_time; gchar *preferred_handler; + McdAccount *account; /* weak ref */ + gboolean proceeding; gboolean use_existing; }; @@ -93,6 +104,9 @@ enum _McdChannelPropertyType { PROP_TP_CHANNEL = 1, PROP_OUTGOING, + PROP_ACCOUNT_PATH, + PROP_REQUESTS, + PROP_USER_ACTION_TIME, }; #define DEPRECATED_PROPERTY_WARNING \ @@ -333,9 +347,46 @@ _mcd_channel_get_property (GObject * obj, guint prop_id, case PROP_TP_CHANNEL: g_value_set_object (val, priv->tp_chan); break; + case PROP_OUTGOING: g_value_set_boolean (val, priv->outgoing); break; + + case PROP_ACCOUNT_PATH: + if (priv->request_data != NULL && + priv->request_data->account != NULL) + { + g_value_set_boxed (val, + mcd_account_get_object_path (priv->request_data->account)); + break; + } + g_value_set_static_boxed (val, "/"); + break; + + case PROP_USER_ACTION_TIME: + if (priv->request_data != NULL) + { + g_value_set_int64 (val, priv->request_data->user_time); + break; + } + g_value_set_int64 (val, 0); + break; + + case PROP_REQUESTS: + if (priv->request_data != NULL && + priv->request_data->properties != NULL) + { + GPtrArray *arr = g_ptr_array_sized_new (1); + + g_ptr_array_add (arr, + g_hash_table_ref (priv->request_data->properties)); + + g_value_take_boxed (val, arr); + break; + } + g_value_take_boxed (val, g_ptr_array_sized_new (0)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); break; @@ -357,6 +408,22 @@ _mcd_channel_constructed (GObject * object) } static void +mcd_channel_lost_account (gpointer data, + GObject *ex_account) +{ + McdChannel *self = MCD_CHANNEL (data); + + DEBUG ("%p: %p", self, ex_account); + + g_assert (self->priv->request_data != NULL); + g_assert ((gpointer) self->priv->request_data->account == + (gpointer) ex_account); + g_assert (self->priv->status == MCD_CHANNEL_STATUS_FAILED); + + self->priv->request_data->account = NULL; +} + +static void _mcd_channel_dispose (GObject * object) { McdChannelPrivate *priv = MCD_CHANNEL_PRIV (object); @@ -369,6 +436,12 @@ _mcd_channel_dispose (GObject * object) if (priv->request_data) { + if (priv->request_data->account != NULL) + { + g_object_weak_unref ((GObject *) priv->request_data->account, + mcd_channel_lost_account, object); + } + channel_request_data_free (priv->request_data); priv->request_data = NULL; } @@ -438,6 +511,20 @@ mcd_channel_status_changed (McdChannel *channel, McdChannelStatus status) static void mcd_channel_class_init (McdChannelClass * klass) { + static TpDBusPropertiesMixinPropImpl request_props[] = { + { "Account", "account-path", NULL }, + { "UserActionTime", "user-action-time", NULL }, + { "Requests", "requests", NULL }, + { NULL } + }; + static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = { + { MC_IFACE_CHANNEL_REQUEST, + tp_dbus_properties_mixin_getter_gobject_properties, + NULL, + request_props, + }, + { NULL } + }; GObjectClass *object_class = G_OBJECT_CLASS (klass); McdMissionClass *mission_class = MCD_MISSION_CLASS (klass); g_type_class_add_private (object_class, sizeof (McdChannelPrivate)); @@ -483,6 +570,34 @@ mcd_channel_class_init (McdChannelClass * klass) "True if the channel was requested by us", FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_ACCOUNT_PATH, + g_param_spec_boxed ("account-path", + "Account", + "Object path of the Account", + DBUS_TYPE_G_OBJECT_PATH, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property + (object_class, PROP_USER_ACTION_TIME, + g_param_spec_int64 ("user-action-time", + "UserActionTime", + "Time of user action in seconds since 1970", + G_MININT64, G_MAXINT64, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property + (object_class, PROP_REQUESTS, + g_param_spec_boxed ("requests", + "Requests", + "A dbus-glib aa{sv}", + TP_ARRAY_TYPE_QUALIFIED_PROPERTY_VALUE_MAP_LIST, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + klass->dbus_properties_class.interfaces = prop_interfaces, + tp_dbus_properties_mixin_class_init (object_class, + G_STRUCT_OFFSET (McdChannelClass, dbus_properties_class)); } static void @@ -582,6 +697,14 @@ mcd_channel_new_from_path (TpConnection *connection, const gchar *object_path, return channel; } +McdChannel * +_mcd_channel_new_undispatched (void) +{ + return g_object_new (MCD_TYPE_CHANNEL, + "outgoing", FALSE, + NULL); +} + gboolean _mcd_channel_create_proxy_old (McdChannel *channel, TpConnection *connection, const gchar *object_path, const gchar *type, @@ -653,7 +776,7 @@ _mcd_channel_create_proxy (McdChannel *channel, TpConnection *connection, } void -mcd_channel_set_status (McdChannel *channel, McdChannelStatus status) +_mcd_channel_set_status (McdChannel *channel, McdChannelStatus status) { DEBUG ("%p, %u", channel, status); g_return_if_fail(MCD_IS_CHANNEL(channel)); @@ -997,7 +1120,7 @@ mcd_channel_take_error (McdChannel *channel, GError *error) g_object_set_data_full ((GObject *)channel, CD_ERROR, error, (GDestroyNotify)g_error_free); if (error) - mcd_channel_set_status (channel, MCD_CHANNEL_STATUS_FAILED); + _mcd_channel_set_status (channel, MCD_CHANNEL_STATUS_FAILED); } /** @@ -1015,9 +1138,13 @@ mcd_channel_get_error (McdChannel *channel) /** * mcd_channel_new_request: + * @account: an account. + * @dgc: a #DBusGConnection on which to export the ChannelRequest object. * @properties: a #GHashTable of desired channel properties. * @user_time: user action time. * @preferred_handler: well-known name of preferred handler. + * @use_existing: use EnsureChannel if %TRUE or CreateChannel if %FALSE + * @proceeding: behave as though Proceed has already been called * * Create a #McdChannel object holding the given properties. The object can * then be used to intiate a channel request, by passing it to @@ -1026,8 +1153,13 @@ mcd_channel_get_error (McdChannel *channel) * Returns: a newly created #McdChannel. */ McdChannel * -mcd_channel_new_request (GHashTable *properties, guint64 user_time, - const gchar *preferred_handler) +mcd_channel_new_request (McdAccount *account, + DBusGConnection *dgc, + GHashTable *properties, + gint64 user_time, + const gchar *preferred_handler, + gboolean use_existing, + gboolean proceeding) { McdChannel *channel; McdChannelRequestData *crd; @@ -1037,17 +1169,32 @@ mcd_channel_new_request (GHashTable *properties, guint64 user_time, NULL); /* TODO: these data could be freed when the channel status becomes - * MCD_CHANNEL_STATUS_DISPATCHED */ + * MCD_CHANNEL_STATUS_DISPATCHED or MCD_CHANNEL_STATUS_FAILED */ crd = g_slice_new (McdChannelRequestData); crd->path = g_strdup_printf (REQUEST_OBJ_BASE "%u", last_req_id++); crd->properties = g_hash_table_ref (properties); crd->user_time = user_time; crd->preferred_handler = g_strdup (preferred_handler); + crd->use_existing = use_existing; + crd->proceeding = proceeding; + + /* the McdAccount almost certainly lives longer than we do, but in case it + * doesn't, use a weak ref here */ + g_object_weak_ref ((GObject *) account, mcd_channel_lost_account, + channel); + crd->account = account; + channel->priv->request_data = crd; channel->priv->satisfied_requests = g_list_prepend (NULL, g_strdup (crd->path)); - mcd_channel_set_status (channel, MCD_CHANNEL_STATUS_REQUEST); + _mcd_channel_set_status (channel, MCD_CHANNEL_STATUS_REQUEST); + + /* This could do with refactoring so that requests are a separate object + * that dies at the appropriate time, but for now the path of least + * resistance is to have the McdChannel be a ChannelRequest throughout + * its lifetime */ + dbus_g_connection_register_g_object (dgc, crd->path, (GObject *) channel); return channel; } @@ -1138,25 +1285,6 @@ _mcd_channel_get_request_preferred_handler (McdChannel *channel) } /* - * _mcd_channel_set_request_use_existing: - * @channel: the #McdChannel. - * @use_existing: %TRUE if @channel must be requested via EnsureChannel. - * - * Sets the use_existing flag on @channel request. - */ -void -_mcd_channel_set_request_use_existing (McdChannel *channel, - gboolean use_existing) -{ - McdChannelRequestData *crd; - - g_return_if_fail (MCD_IS_CHANNEL (channel)); - crd = channel->priv->request_data; - if (G_UNLIKELY (!crd)) return; - crd->use_existing = use_existing; -} - -/* * _mcd_channel_get_request_use_existing: * @channel: the #McdChannel. * @@ -1225,7 +1353,7 @@ copy_status (McdChannel *source, McdChannel *dest) mcd_channel_take_error (dest, g_error_copy (error)); } else - mcd_channel_set_status (dest, src_priv->status); + _mcd_channel_set_status (dest, src_priv->status); } if (dst_priv->status == MCD_CHANNEL_STATUS_FAILED || @@ -1303,3 +1431,73 @@ mcd_channel_get_tp_channel (McdChannel *channel) return channel->priv->tp_chan; } +static void +channel_request_proceed (McSvcChannelRequest *iface, + DBusGMethodInvocation *context) +{ + McdChannel *self = MCD_CHANNEL (iface); + + if (G_UNLIKELY (self->priv->request_data == NULL)) + { + GError na = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "McdChannel is on D-Bus but is not actually a request" }; + + /* shouldn't be possible, but this code is quite tangled */ + g_warning ("%s: channel %p is on D-Bus but not actually a request", + G_STRFUNC, self); + dbus_g_method_return_error (context, &na); + return; + } + + if (G_UNLIKELY (self->priv->request_data->account == NULL)) + { + GError na = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "McdChannel has no Account, cannot proceed" }; + + /* likewise, shouldn't be possible but this code is quite tangled */ + g_warning ("%s: channel %p has no Account, so cannot proceed", + G_STRFUNC, self); + dbus_g_method_return_error (context, &na); + return; + } + + if (self->priv->request_data->proceeding) + { + GError na = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "Proceed has already been called; stop calling it" }; + + dbus_g_method_return_error (context, &na); + } + + self->priv->request_data->proceeding = TRUE; + _mcd_account_proceed_with_request (self->priv->request_data->account, + self); + mc_svc_channel_request_return_from_proceed (context); +} + +static void +channel_request_cancel (McSvcChannelRequest *iface, + DBusGMethodInvocation *context) +{ + McdChannel *self = MCD_CHANNEL (iface); + GError ni = { TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, + "Cancel not yet implemented" }; + +#if 0 + mc_svc_channel_request_return_from_cancel (context); +#endif + + (void) self; + dbus_g_method_return_error (context, &ni); +} + +static void +request_iface_init (gpointer g_iface, + gpointer iface_data G_GNUC_UNUSED) +{ +#define IMPLEMENT(x) mc_svc_channel_request_implement_##x (\ + g_iface, channel_request_##x) + IMPLEMENT (proceed); + IMPLEMENT (cancel); +#undef IMPLEMENT +} diff --git a/src/mcd-channel.h b/src/mcd-channel.h index f0742ce6..268c8363 100644 --- a/src/mcd-channel.h +++ b/src/mcd-channel.h @@ -29,6 +29,7 @@ #include <glib.h> #include <glib-object.h> #include <telepathy-glib/channel.h> +#include <telepathy-glib/dbus-properties-mixin.h> #include "mcd-mission.h" @@ -78,6 +79,7 @@ struct _McdChannelClass void (*status_changed_signal) (McdChannel * channel, McdChannelStatus status); void (*members_accepted_signal) (McdChannel *channel); + TpDBusPropertiesMixinClass dbus_properties_class; void (*_mc_reserved1) (void); void (*_mc_reserved2) (void); void (*_mc_reserved3) (void); @@ -95,9 +97,14 @@ McdChannel *mcd_channel_new_from_path (TpConnection *connection, const gchar *object_path, const gchar *type, guint handle, TpHandleType handle_type); -McdChannel *mcd_channel_new_request (GHashTable *properties, - guint64 user_time, - const gchar *preferred_handler); +McdChannel *mcd_channel_new_request (McdAccount *account, + DBusGConnection *dgc, + GHashTable *properties, + gint64 user_time, + const gchar *preferred_handler, + gboolean use_existing, + gboolean proceeding); +G_GNUC_INTERNAL McdChannel *_mcd_channel_new_undispatched (void); G_GNUC_INTERNAL gboolean _mcd_channel_create_proxy (McdChannel *channel, @@ -105,7 +112,9 @@ gboolean _mcd_channel_create_proxy (McdChannel *channel, const gchar *object_path, const GHashTable *properties); -void mcd_channel_set_status (McdChannel *channel, McdChannelStatus status); +G_GNUC_INTERNAL +void _mcd_channel_set_status (McdChannel *channel, McdChannelStatus status); + McdChannelStatus mcd_channel_get_status (McdChannel * channel); gboolean mcd_channel_get_members_accepted (McdChannel *channel); const gchar* mcd_channel_get_channel_type (McdChannel *channel); @@ -155,9 +164,6 @@ guint64 _mcd_channel_get_request_user_action_time (McdChannel *channel); G_GNUC_INTERNAL const gchar *_mcd_channel_get_request_preferred_handler (McdChannel *channel); G_GNUC_INTERNAL -void _mcd_channel_set_request_use_existing (McdChannel *channel, - gboolean use_existing); -G_GNUC_INTERNAL gboolean _mcd_channel_get_request_use_existing (McdChannel *channel); G_GNUC_INTERNAL diff --git a/src/mcd-connection.c b/src/mcd-connection.c index aab6dd1a..33a78dec 100644 --- a/src/mcd-connection.c +++ b/src/mcd-connection.c @@ -495,9 +495,7 @@ on_new_channel (TpConnection *proxy, const gchar *chan_obj_path, * to be used later */ McdTmpChannelData *tcd; - channel = g_object_new (MCD_TYPE_CHANNEL, - "outgoing", FALSE, - NULL); + channel = _mcd_channel_new_undispatched (); tcd = g_slice_new (McdTmpChannelData); tcd->object_path = g_strdup (chan_obj_path); tcd->channel_type = g_strdup (chan_type); @@ -507,7 +505,6 @@ on_new_channel (TpConnection *proxy, const gchar *chan_obj_path, tcd, mcd_tmp_channel_data_free); mcd_operation_take_mission (MCD_OPERATION (connection), MCD_MISSION (channel)); - mcd_channel_set_status (channel, MCD_CHANNEL_STATUS_UNDISPATCHED); } } @@ -1878,7 +1875,7 @@ _mcd_connection_request_channel (McdConnection *connection, ret = request_channel_old_iface (connection, channel); if (ret) - mcd_channel_set_status (channel, MCD_CHANNEL_STATUS_REQUESTED); + _mcd_channel_set_status (channel, MCD_CHANNEL_STATUS_REQUESTED); return ret; } diff --git a/src/mcd-dispatcher.c b/src/mcd-dispatcher.c index 94f35972..acc8dd54 100644 --- a/src/mcd-dispatcher.c +++ b/src/mcd-dispatcher.c @@ -43,6 +43,7 @@ #include <dbus/dbus-glib-lowlevel.h> #include "mcd-signals-marshal.h" +#include "mcd-account-requests.h" #include "mcd-connection.h" #include "mcd-channel.h" #include "mcd-master.h" @@ -815,7 +816,7 @@ _mcd_dispatcher_handle_channel_async_cb (DBusGProxy * proxy, GError * error, g_error_free (unique_proxy_error); } - mcd_channel_set_status (channel, MCD_CHANNEL_STATUS_DISPATCHED); + _mcd_channel_set_status (channel, MCD_CHANNEL_STATUS_DISPATCHED); g_signal_emit_by_name (context->dispatcher, "dispatched", channel); mcd_dispatcher_context_handler_done (context); } @@ -1157,7 +1158,7 @@ handle_channels_cb (TpProxy *proxy, const GError *error, gpointer user_data, McdChannel *channel = MCD_CHANNEL (list->data); /* TODO: abort the channel if the handler dies */ - mcd_channel_set_status (channel, MCD_CHANNEL_STATUS_DISPATCHED); + _mcd_channel_set_status (channel, MCD_CHANNEL_STATUS_DISPATCHED); g_signal_emit_by_name (context->dispatcher, "dispatched", channel); } } @@ -1287,8 +1288,8 @@ mcd_dispatcher_run_handler (McdDispatcherContext *context, if (user_time) user_action_time = user_time; - mcd_channel_set_status (channel, - MCD_CHANNEL_STATUS_HANDLER_INVOKED); + _mcd_channel_set_status (channel, + MCD_CHANNEL_STATUS_HANDLER_INVOKED); } /* The callback needs to get the dispatcher context, and the channels @@ -1651,7 +1652,7 @@ on_operation_finished (McdDispatchOperation *operation, McdChannel *channel = MCD_CHANNEL (list->data); /* TODO: abort the channel if the handler dies */ - mcd_channel_set_status (channel, MCD_CHANNEL_STATUS_DISPATCHED); + _mcd_channel_set_status (channel, MCD_CHANNEL_STATUS_DISPATCHED); g_signal_emit_by_name (context->dispatcher, "dispatched", channel); } @@ -2658,7 +2659,7 @@ mcd_dispatcher_class_init (McdDispatcherClass * klass) client_ready_quark = g_quark_from_static_string ("mcd_client_ready"); - klass->dbus_properties_class.real.interfaces = prop_interfaces, + klass->dbus_properties_class.interfaces = prop_interfaces, tp_dbus_properties_mixin_class_init (object_class, G_STRUCT_OFFSET (McdDispatcherClass, dbus_properties_class)); } @@ -3202,8 +3203,8 @@ _mcd_dispatcher_send_channels (McdDispatcher *dispatcher, GList *channels, GList *list; for (list = channels; list != NULL; list = list->next) - mcd_channel_set_status (MCD_CHANNEL (list->data), - MCD_CHANNEL_STATUS_DISPATCHING); + _mcd_channel_set_status (MCD_CHANNEL (list->data), + MCD_CHANNEL_STATUS_DISPATCHING); _mcd_dispatcher_enter_state_machine (dispatcher, channels, requested); } @@ -3459,8 +3460,8 @@ check_handled_channels (gpointer object, const GError *error, { DEBUG ("Channel %s is handled by %s", path, client->name); cr->handled = TRUE; - mcd_channel_set_status (cr->channel, - MCD_CHANNEL_STATUS_DISPATCHED); + _mcd_channel_set_status (cr->channel, + MCD_CHANNEL_STATUS_DISPATCHED); break; } } @@ -3507,6 +3508,72 @@ _mcd_dispatcher_recover_channel (McdDispatcher *dispatcher, } static void +dispatcher_request_channel (McdDispatcher *self, + const gchar *account_path, + GHashTable *requested_properties, + gint64 user_action_time, + const gchar *preferred_handler, + DBusGMethodInvocation *context, + gboolean ensure) +{ + McdAccountManager *am; + McdAccount *account; + McdChannel *channel; + GError *error = NULL; + const gchar *path; + + g_object_get (self->priv->master, + "account-manager", &am, + NULL); + + g_assert (am != NULL); + + account = mcd_account_manager_lookup_account_by_path (am, + account_path); + + if (account == NULL) + { + g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "No such account: %s", account_path); + goto despair; + } + + /* FIXME: raise InvalidArgument if preferred_handler is syntactically + * invalid or does not start with the right thing */ + + channel = _mcd_account_create_request (account, requested_properties, + user_action_time, preferred_handler, + ensure, FALSE, &error); + + if (channel == NULL) + { + /* FIXME: ideally this would be emitted as a Failed signal after + * Proceed is called, but for the particular failure case here (low + * memory) perhaps we don't want to */ + goto despair; + } + + path = _mcd_channel_get_request_path (channel); + + g_assert (path != NULL); + + /* This is OK because the signatures of CreateChannel and EnsureChannel + * are the same */ + mc_svc_channel_dispatcher_return_from_create_channel (context, path); + + _mcd_dispatcher_add_request (self, account, channel); + + /* We've done all we need to with this channel: the ChannelRequests code + * keeps it alive as long as is necessary */ + g_object_unref (channel); + return; + +despair: + dbus_g_method_return_error (context, error); + g_error_free (error); +} + +static void dispatcher_create_channel (McSvcChannelDispatcher *iface, const gchar *account_path, GHashTable *requested_properties, @@ -3514,12 +3581,13 @@ dispatcher_create_channel (McSvcChannelDispatcher *iface, const gchar *preferred_handler, DBusGMethodInvocation *context) { - McdDispatcher *self = MCD_DISPATCHER (iface); - GError ni = { TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, - "CreateChannel not yet implemented" }; - - (void) self; - dbus_g_method_return_error (context, &ni); + dispatcher_request_channel (MCD_DISPATCHER (iface), + account_path, + requested_properties, + user_action_time, + preferred_handler, + context, + FALSE); } static void @@ -3530,12 +3598,13 @@ dispatcher_ensure_channel (McSvcChannelDispatcher *iface, const gchar *preferred_handler, DBusGMethodInvocation *context) { - McdDispatcher *self = MCD_DISPATCHER (iface); - GError ni = { TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, - "EnsureChannel not yet implemented" }; - - (void) self; - dbus_g_method_return_error (context, &ni); + dispatcher_request_channel (MCD_DISPATCHER (iface), + account_path, + requested_properties, + user_action_time, + preferred_handler, + context, + TRUE); } static void diff --git a/src/mcd-dispatcher.h b/src/mcd-dispatcher.h index d39371a5..3eb764c6 100644 --- a/src/mcd-dispatcher.h +++ b/src/mcd-dispatcher.h @@ -74,10 +74,8 @@ struct _McdDispatcherClass GError *error); /* virtual methods */ - union { - TpDBusPropertiesMixinClass real; - GCallback _pad; - } dbus_properties_class; + TpDBusPropertiesMixinClass dbus_properties_class; + void (*_mc_reserved0) (void); void (*_mc_reserved1) (void); void (*_mc_reserved2) (void); void (*_mc_reserved3) (void); diff --git a/test/twisted/Makefile.am b/test/twisted/Makefile.am index ba873e9e..678f4e0d 100644 --- a/test/twisted/Makefile.am +++ b/test/twisted/Makefile.am @@ -3,6 +3,9 @@ TWISTED_TESTS = TWISTED_BASIC_TESTS = \ account-bad-cm.py \ account-requests/create-text.py \ + account-requests/delete-account-during-request.py \ + dispatcher/already-has-channel.py \ + dispatcher/create-text.py \ dispatcher/dispatch-text.py \ do-nothing.py \ test-account.py \ diff --git a/test/twisted/account-requests/create-text.py b/test/twisted/account-requests/create-text.py index 642d0e75..3bbb1174 100644 --- a/test/twisted/account-requests/create-text.py +++ b/test/twisted/account-requests/create-text.py @@ -85,6 +85,15 @@ def test_channel_creation(q, bus, account, client, conn, ensure): request_path = ret.value[0] + cr = bus.get_object(cs.AM, request_path) + # FIXME: MC gives CR properties to clients without .DRAFT, but the + # CR itself is really still .DRAFT + request_props = cr.GetAll(cs.CR + '.DRAFT', + dbus_interface=cs.PROPERTIES_IFACE) + assert request_props['Account'] == account.object_path + assert request_props['Requests'] == [request] + assert request_props['UserActionTime'] == user_action_time + # ChannelDispatcher calls AddRequest on chat UI; chat UI ignores it as the # request is already known to it. # FIXME: it is not, strictly speaking, an API guarantee that the Requests @@ -96,8 +105,10 @@ def test_channel_creation(q, bus, account, client, conn, ensure): request_props = e.args[1] assert request_props[cs.CR + '.Account'] == account.object_path assert request_props[cs.CR + '.Requests'] == [request] - assert request_props[cs.CR + '.PreferredHandler'] == client.bus_name assert request_props[cs.CR + '.UserActionTime'] == user_action_time + # FIXME: this is not actually in telepathy-spec (although maybe it + # should be) - fd.o #21013 + assert request_props[cs.CR + '.PreferredHandler'] == client.bus_name q.dbus_return(e.message, signature='') @@ -154,9 +165,13 @@ def test_channel_creation(q, bus, account, client, conn, ensure): q.dbus_return(e.message, signature='') # CR emits Succeeded (or in Mardy's version, Account emits Succeeded) - e = q.expect('dbus-signal', path=account.object_path, - interface=cs.ACCOUNT_IFACE_NOKIA_REQUESTS, signal='Succeeded', - args=[request_path]) + q.expect_many( + EventPattern('dbus-signal', path=account.object_path, + interface=cs.ACCOUNT_IFACE_NOKIA_REQUESTS, signal='Succeeded', + args=[request_path]), + EventPattern('dbus-signal', path=request_path, + interface=cs.CR + '.DRAFT', signal='Succeeded'), + ) # Close the channel channel.close() diff --git a/test/twisted/account-requests/delete-account-during-request.py b/test/twisted/account-requests/delete-account-during-request.py new file mode 100644 index 00000000..942e4755 --- /dev/null +++ b/test/twisted/account-requests/delete-account-during-request.py @@ -0,0 +1,136 @@ +"""Regression test for the unofficial Account.Interface.Requests API, when +an account is deleted while requesting a channel from that account. +""" + +import dbus +import dbus.service + +from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \ + call_async +from mctest import exec_test, SimulatedConnection, SimulatedClient, \ + create_fakecm_account, enable_fakecm_account, SimulatedChannel +import constants as cs + +def test(q, bus, mc): + params = dbus.Dictionary({"account": "someguy@example.com", + "password": "secrecy"}, signature='sv') + cm_name_ref, account = create_fakecm_account(q, bus, mc, params) + conn = enable_fakecm_account(q, bus, mc, account, params) + + text_fixed_properties = dbus.Dictionary({ + cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT, + cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT, + }, signature='sv') + + client = SimulatedClient(q, bus, 'Empathy', + observe=[text_fixed_properties], approve=[text_fixed_properties], + handle=[text_fixed_properties], bypass_approval=False) + + # wait for MC to download the properties + q.expect_many( + EventPattern('dbus-method-call', + interface=cs.PROPERTIES_IFACE, method='Get', + args=[cs.CLIENT, 'Interfaces'], + path=client.object_path), + EventPattern('dbus-method-call', + interface=cs.PROPERTIES_IFACE, method='Get', + args=[cs.APPROVER, 'ApproverChannelFilter'], + path=client.object_path), + EventPattern('dbus-method-call', + interface=cs.PROPERTIES_IFACE, method='Get', + args=[cs.HANDLER, 'HandlerChannelFilter'], + path=client.object_path), + EventPattern('dbus-method-call', + interface=cs.PROPERTIES_IFACE, method='Get', + args=[cs.OBSERVER, 'ObserverChannelFilter'], + path=client.object_path), + ) + + user_action_time = dbus.Int64(1238582606) + + # chat UI calls ChannelDispatcher.CreateChannel + # (or in this case, an equivalent non-standard method on the Account) + request = dbus.Dictionary({ + cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT, + cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT, + cs.CHANNEL + '.TargetID': 'juliet', + }, signature='sv') + account_requests = dbus.Interface(account, + cs.ACCOUNT_IFACE_NOKIA_REQUESTS) + call_async(q, account_requests, 'Create', + request, user_action_time, client.bus_name) + + # chat UI connects to signals and calls ChannelRequest.Proceed() - but not + # in this non-standard API, which fires off the request instantly + ret, cm_request_call = q.expect_many( + EventPattern('dbus-return', + method='Create'), + EventPattern('dbus-method-call', + interface=cs.CONN_IFACE_REQUESTS, + method='CreateChannel', + path=conn.object_path, args=[request], handled=False), + ) + + request_path = ret.value[0] + + e = q.expect('dbus-method-call', handled=False, + interface=cs.HANDLER, method='AddRequest', path=client.object_path) + assert e.args[0] == request_path + + q.dbus_return(e.message, signature='') + + # Before the channel is returned, we delete the account + + account_iface = dbus.Interface(account, cs.ACCOUNT) + assert account_iface.Remove() is None + account_event, account_manager_event = q.expect_many( + EventPattern('dbus-signal', + path=account.object_path, + signal='Removed', + interface=cs.ACCOUNT, + args=[] + ), + EventPattern('dbus-signal', + path=cs.AM_PATH, + signal='AccountRemoved', + interface=cs.AM, + args=[account.object_path] + ), + ) + + # You know that request I told you about? Not going to happen. + remove_failed_request = q.expect('dbus-method-call', + interface=cs.HANDLER, + method='RemoveFailedRequest', + handled=False) + assert remove_failed_request.args[0] == request_path + # FIXME: the spec should maybe define what error this will be. Currently, + # it's Disconnected + assert remove_failed_request.args[1].startswith(tp_name_prefix + '.Error.') + + q.expect_many( + EventPattern('dbus-signal', + path=request_path, + interface=cs.CR + '.DRAFT', + signal='Failed', + args=remove_failed_request.args[1:]), + EventPattern('dbus-signal', + path=account.object_path, + interface=cs.ACCOUNT_IFACE_NOKIA_REQUESTS, + signal='Failed', + args=remove_failed_request.args), + ) + + q.dbus_return(remove_failed_request.message, signature='') + + # ... and the Connection is told to disconnect, hopefully before the + # Channel has actually been established + e = q.expect('dbus-method-call', + path=conn.object_path, + interface=cs.CONN, + method='Disconnect', + args=[], + handled=True) + +if __name__ == '__main__': + exec_test(test, {}) diff --git a/test/twisted/dispatcher/already-has-channel.py b/test/twisted/dispatcher/already-has-channel.py new file mode 100644 index 00000000..19e63652 --- /dev/null +++ b/test/twisted/dispatcher/already-has-channel.py @@ -0,0 +1,235 @@ +"""Regression test for dispatching an incoming Text channel that was already +there before the Connection became ready. +""" + +import dbus +import dbus.service + +from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \ + call_async +from mctest import exec_test, SimulatedConnection, SimulatedClient, \ + create_fakecm_account, SimulatedChannel +import constants as cs + +def test(q, bus, mc): + params = dbus.Dictionary({"account": "someguy@example.com", + "password": "secrecy"}, signature='sv') + cm_name_ref, account = create_fakecm_account(q, bus, mc, params) + + text_fixed_properties = dbus.Dictionary({ + cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT, + cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT, + }, signature='sv') + + # Two clients want to observe, approve and handle channels + empathy = SimulatedClient(q, bus, 'Empathy', + observe=[text_fixed_properties], approve=[text_fixed_properties], + handle=[text_fixed_properties], bypass_approval=False) + kopete = SimulatedClient(q, bus, 'Kopete', + observe=[text_fixed_properties], approve=[text_fixed_properties], + handle=[text_fixed_properties], bypass_approval=False) + + # wait for MC to download the properties + q.expect_many( + EventPattern('dbus-method-call', + interface=cs.PROPERTIES_IFACE, method='Get', + args=[cs.CLIENT, 'Interfaces'], + path=empathy.object_path), + EventPattern('dbus-method-call', + interface=cs.PROPERTIES_IFACE, method='Get', + args=[cs.APPROVER, 'ApproverChannelFilter'], + path=empathy.object_path), + EventPattern('dbus-method-call', + interface=cs.PROPERTIES_IFACE, method='Get', + args=[cs.HANDLER, 'HandlerChannelFilter'], + path=empathy.object_path), + EventPattern('dbus-method-call', + interface=cs.PROPERTIES_IFACE, method='Get', + args=[cs.OBSERVER, 'ObserverChannelFilter'], + path=empathy.object_path), + + EventPattern('dbus-method-call', + interface=cs.PROPERTIES_IFACE, method='Get', + args=[cs.CLIENT, 'Interfaces'], + path=kopete.object_path), + EventPattern('dbus-method-call', + interface=cs.PROPERTIES_IFACE, method='Get', + args=[cs.APPROVER, 'ApproverChannelFilter'], + path=kopete.object_path), + EventPattern('dbus-method-call', + interface=cs.PROPERTIES_IFACE, method='Get', + args=[cs.HANDLER, 'HandlerChannelFilter'], + path=kopete.object_path), + EventPattern('dbus-method-call', + interface=cs.PROPERTIES_IFACE, method='Get', + args=[cs.OBSERVER, 'ObserverChannelFilter'], + path=kopete.object_path), + ) + + # Enable the account + account.Set(cs.ACCOUNT, 'Enabled', True, + dbus_interface=cs.PROPERTIES_IFACE) + + requested_presence = dbus.Struct((dbus.UInt32(2L), + dbus.String(u'available'), dbus.String(u''))) + account.Set(cs.ACCOUNT, + 'RequestedPresence', requested_presence, + dbus_interface=cs.PROPERTIES_IFACE) + + e = q.expect('dbus-method-call', method='RequestConnection', + args=['fakeprotocol', params], + destination=cs.tp_name_prefix + '.ConnectionManager.fakecm', + path=cs.tp_path_prefix + '/ConnectionManager/fakecm', + interface=cs.tp_name_prefix + '.ConnectionManager', + handled=False) + + # Don't allow the Connection to become ready until we want it to, by + # avoiding a return from GetInterfaces + conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol', '_', + 'myself', implement_get_interfaces=False) + + q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so') + + q.expect('dbus-method-call', method='Connect', + path=conn.object_path, handled=True) + conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE) + + get_interfaces_call = q.expect('dbus-method-call', method='GetInterfaces', + path=conn.object_path, handled=False) + + # subscribe to the OperationList interface (MC assumes that until this + # property has been retrieved once, nobody cares) + + cd = bus.get_object(cs.CD_BUS_NAME, cs.CD_PATH) + cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE) + assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == [] + + # Before returning from GetInterfaces, make a Channel spring into + # existence + + channel_properties = dbus.Dictionary(text_fixed_properties, + signature='sv') + channel_properties[cs.CHANNEL + '.TargetID'] = 'juliet' + channel_properties[cs.CHANNEL + '.TargetHandle'] = \ + conn.ensure_handle(cs.HT_CONTACT, 'juliet') + channel_properties[cs.CHANNEL + '.InitiatorID'] = 'juliet' + channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \ + conn.ensure_handle(cs.HT_CONTACT, 'juliet') + channel_properties[cs.CHANNEL + '.Requested'] = False + channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s') + + chan = SimulatedChannel(conn, channel_properties) + chan.announce() + + # Now reply to GetInterfaces and say we have Requests + conn.GetInterfaces(get_interfaces_call) + q.expect_many( + EventPattern('dbus-method-call', + interface=cs.PROPERTIES_IFACE, method='GetAll', + args=[cs.CONN_IFACE_REQUESTS], + path=conn.object_path, handled=True), + ) + + # A channel dispatch operation is created for the channel we already had + + e = q.expect('dbus-signal', + path=cs.CD_PATH, + interface=cs.CD_IFACE_OP_LIST, + signal='NewDispatchOperation') + + cdo_path = e.args[0] + cdo_properties = e.args[1] + + assert cdo_properties[cs.CDO + '.Account'] == account.object_path + assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path + + handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:] + handlers.sort() + # FIXME: not true + #assert handlers == [cs.tp_name_prefix + '.Client.Empathy', + # cs.tp_name_prefix + '.Client.Kopete'], handlers + + assert cdo_properties[cs.CDO + '.Channels'] == [(chan.object_path, + channel_properties)] + + assert cs.CD_IFACE_OP_LIST in cd_props.Get(cs.CD, 'Interfaces') + assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') ==\ + [(cdo_path, cdo_properties)] + + cdo = bus.get_object(cs.CD_BUS_NAME, cdo_path) + cdo_iface = dbus.Interface(cdo, cs.CDO) + + # Both Observers are told about the new channel + + e, k = q.expect_many( + EventPattern('dbus-method-call', + path=empathy.object_path, + interface=cs.OBSERVER, method='ObserveChannels', + handled=False), + EventPattern('dbus-method-call', + path=kopete.object_path, + interface=cs.OBSERVER, method='ObserveChannels', + handled=False), + ) + assert e.args[0] == account.object_path, e.args + assert e.args[1] == conn.object_path, e.args + channels = e.args[2] + assert len(channels) == 1, channels + assert channels[0][0] == chan.object_path, channels + assert channels[0][1] == channel_properties, channels + + assert k.args == e.args + + # Both Observers indicate that they are ready to proceed + q.dbus_return(k.message, signature='') + q.dbus_return(e.message, signature='') + + # The Approvers are next + + e, k = q.expect_many( + EventPattern('dbus-method-call', + path=empathy.object_path, + interface=cs.APPROVER, method='AddDispatchOperation', + handled=False), + EventPattern('dbus-method-call', + path=kopete.object_path, + interface=cs.APPROVER, method='AddDispatchOperation', + handled=False), + ) + + assert e.args == [cdo_path, cdo_properties] + assert k.args == [cdo_path, cdo_properties] + + q.dbus_return(e.message, signature='') + q.dbus_return(k.message, signature='') + + # Both Approvers now have a flashing icon or something, trying to get the + # user's attention + + # The user responds to Empathy first + call_async(q, cdo_iface, 'HandleWith', + cs.tp_name_prefix + '.Client.Empathy') + + # Empathy is asked to handle the channels + e = q.expect('dbus-method-call', + path=empathy.object_path, + interface=cs.HANDLER, method='HandleChannels', + handled=False) + + # Empathy accepts the channels + q.dbus_return(e.message, signature='') + + # FIXME: this shouldn't happen until after HandleChannels has succeeded, + # but MC currently does this as soon as HandleWith is called (fd.o #21003) + #q.expect('dbus-signal', path=cdo_path, signal='Finished') + #q.expect('dbus-signal', path=cs.CD_PATH, + # signal='DispatchOperationFinished', args=[cdo_path]) + + # HandleWith succeeds + q.expect('dbus-return', method='HandleWith') + + # Now there are no more active channel dispatch operations + assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == [] + +if __name__ == '__main__': + exec_test(test, {}) diff --git a/test/twisted/dispatcher/create-text.py b/test/twisted/dispatcher/create-text.py new file mode 100644 index 00000000..528f42b3 --- /dev/null +++ b/test/twisted/dispatcher/create-text.py @@ -0,0 +1,184 @@ +"""Regression test for the unofficial Account.Interface.Requests API when +a channel can be created successfully. +""" + +import dbus +import dbus.service + +from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \ + call_async +from mctest import exec_test, SimulatedConnection, SimulatedClient, \ + create_fakecm_account, enable_fakecm_account, SimulatedChannel +import constants as cs + +def test(q, bus, mc): + params = dbus.Dictionary({"account": "someguy@example.com", + "password": "secrecy"}, signature='sv') + cm_name_ref, account = create_fakecm_account(q, bus, mc, params) + conn = enable_fakecm_account(q, bus, mc, account, params) + + text_fixed_properties = dbus.Dictionary({ + cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT, + cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT, + }, signature='sv') + + client = SimulatedClient(q, bus, 'Empathy', + observe=[text_fixed_properties], approve=[text_fixed_properties], + handle=[text_fixed_properties], bypass_approval=False) + + # No Approver should be invoked at any point during this test, because the + # Channel was Requested + def fail_on_approval(e): + raise AssertionError('Approver should not be invoked') + q.add_dbus_method_impl(fail_on_approval, path=client.object_path, + interface=cs.APPROVER, method='AddDispatchOperation') + + # wait for MC to download the properties + q.expect_many( + EventPattern('dbus-method-call', + interface=cs.PROPERTIES_IFACE, method='Get', + args=[cs.CLIENT, 'Interfaces'], + path=client.object_path), + EventPattern('dbus-method-call', + interface=cs.PROPERTIES_IFACE, method='Get', + args=[cs.APPROVER, 'ApproverChannelFilter'], + path=client.object_path), + EventPattern('dbus-method-call', + interface=cs.PROPERTIES_IFACE, method='Get', + args=[cs.HANDLER, 'HandlerChannelFilter'], + path=client.object_path), + EventPattern('dbus-method-call', + interface=cs.PROPERTIES_IFACE, method='Get', + args=[cs.OBSERVER, 'ObserverChannelFilter'], + path=client.object_path), + ) + + test_channel_creation(q, bus, account, client, conn, False) + test_channel_creation(q, bus, account, client, conn, True) + +def test_channel_creation(q, bus, account, client, conn, ensure): + user_action_time = dbus.Int64(1238582606) + + cd = bus.get_object(cs.CD_BUS_NAME, cs.CD_PATH) + cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE) + + # chat UI calls ChannelDispatcher.EnsureChannel or CreateChannel + request = dbus.Dictionary({ + cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT, + cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT, + cs.CHANNEL + '.TargetID': 'juliet', + }, signature='sv') + account_requests = dbus.Interface(account, + cs.ACCOUNT_IFACE_NOKIA_REQUESTS) + call_async(q, cd, + (ensure and 'EnsureChannel' or 'CreateChannel'), + account.object_path, request, user_action_time, client.bus_name, + dbus_interface=cs.CD) + ret = q.expect('dbus-return', + method=(ensure and 'EnsureChannel' or 'CreateChannel')) + request_path = ret.value[0] + + # chat UI connects to signals and calls ChannelRequest.Proceed() + + cr = bus.get_object(cs.AM, request_path) + # FIXME: MC gives CR properties to clients without .DRAFT, but the + # CR itself is really still .DRAFT + request_props = cr.GetAll(cs.CR + '.DRAFT', + dbus_interface=cs.PROPERTIES_IFACE) + assert request_props['Account'] == account.object_path + assert request_props['Requests'] == [request] + assert request_props['UserActionTime'] == user_action_time + + cr.Proceed(dbus_interface=cs.CR + '.DRAFT') + + # FIXME: should the EnsureChannel/CreateChannel call, and the AddRequest + # call, be in a defined order? Probably not though, since CMs and Clients + # aren't meant to be the same process! + + cm_request_call, add_request_call = q.expect_many( + EventPattern('dbus-method-call', + interface=cs.CONN_IFACE_REQUESTS, + method=(ensure and 'EnsureChannel' or 'CreateChannel'), + path=conn.object_path, args=[request], handled=False), + EventPattern('dbus-method-call', handled=False, + interface=cs.HANDLER, method='AddRequest', + path=client.object_path), + ) + + assert add_request_call.args[0] == request_path + request_props = add_request_call.args[1] + assert request_props[cs.CR + '.Account'] == account.object_path + assert request_props[cs.CR + '.Requests'] == [request] + assert request_props[cs.CR + '.UserActionTime'] == user_action_time + # FIXME: this is not actually in telepathy-spec (although maybe it + # should be) - fd.o #21013 + assert request_props[cs.CR + '.PreferredHandler'] == client.bus_name + + q.dbus_return(add_request_call.message, signature='') + + # Time passes. A channel is returned. + + channel_immutable = dbus.Dictionary(request) + channel_immutable[cs.CHANNEL + '.InitiatorID'] = conn.self_ident + channel_immutable[cs.CHANNEL + '.InitiatorHandle'] = conn.self_handle + channel_immutable[cs.CHANNEL + '.Requested'] = True + channel_immutable[cs.CHANNEL + '.Interfaces'] = \ + dbus.Array([], signature='s') + channel_immutable[cs.CHANNEL + '.TargetHandle'] = \ + conn.ensure_handle(cs.HT_CONTACT, 'juliet') + channel = SimulatedChannel(conn, channel_immutable) + + # this order of events is guaranteed by telepathy-spec (since 0.17.14) + if ensure: + q.dbus_return(cm_request_call.message, True, # <- Yours + channel.object_path, channel.immutable, signature='boa{sv}') + else: # Create + q.dbus_return(cm_request_call.message, + channel.object_path, channel.immutable, signature='oa{sv}') + channel.announce() + + # Observer should get told, processing waits for it + e = q.expect('dbus-method-call', + path=client.object_path, + interface=cs.OBSERVER, method='ObserveChannels', + handled=False) + assert e.args[0] == account.object_path, e.args + assert e.args[1] == conn.object_path, e.args + channels = e.args[2] + assert len(channels) == 1, channels + assert channels[0][0] == channel.object_path, channels + assert channels[0][1] == channel_immutable, channels + + # Observer says "OK, go" + q.dbus_return(e.message, signature='') + + # Handler is next + e = q.expect('dbus-method-call', + path=client.object_path, + interface=cs.HANDLER, method='HandleChannels', + handled=False) + assert e.args[0] == account.object_path, e.args + assert e.args[1] == conn.object_path, e.args + channels = e.args[2] + assert len(channels) == 1, channels + assert channels[0][0] == channel.object_path, channels + assert channels[0][1] == channel_immutable, channels + assert e.args[3] == [request_path], e.args + + # Handler accepts the Channels + q.dbus_return(e.message, signature='') + + # CR emits Succeeded (or in Mardy's version, Account emits Succeeded) + q.expect_many( + EventPattern('dbus-signal', path=account.object_path, + interface=cs.ACCOUNT_IFACE_NOKIA_REQUESTS, signal='Succeeded', + args=[request_path]), + EventPattern('dbus-signal', path=request_path, + interface=cs.CR + '.DRAFT', signal='Succeeded'), + ) + + # Close the channel + channel.close() + +if __name__ == '__main__': + exec_test(test, {}) diff --git a/test/twisted/dispatcher/dispatch-text.py b/test/twisted/dispatcher/dispatch-text.py index 1b306bcf..40ad01d4 100644 --- a/test/twisted/dispatcher/dispatch-text.py +++ b/test/twisted/dispatcher/dispatch-text.py @@ -21,7 +21,7 @@ def test(q, bus, mc): cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT, }, signature='sv') - # Two clients want to observe, dispatch and handle channels + # Two clients want to observe, approve and handle channels empathy = SimulatedClient(q, bus, 'Empathy', observe=[text_fixed_properties], approve=[text_fixed_properties], handle=[text_fixed_properties], bypass_approval=False) diff --git a/test/twisted/mctest.py b/test/twisted/mctest.py index 9a0a6a8f..8cde701b 100644 --- a/test/twisted/mctest.py +++ b/test/twisted/mctest.py @@ -134,7 +134,8 @@ class SimulatedConnection(object): self._identifiers[(type, self._last_handle)] = identifier return self._last_handle - def __init__(self, q, bus, cmname, protocol, account_part, self_ident): + def __init__(self, q, bus, cmname, protocol, account_part, self_ident, + implement_get_interfaces=True): self.q = q self.bus = bus @@ -161,9 +162,12 @@ class SimulatedConnection(object): interface=cs.CONN, method='GetSelfHandle') q.add_dbus_method_impl(self.GetStatus, path=self.object_path, interface=cs.CONN, method='GetStatus') - q.add_dbus_method_impl(self.GetInterfaces, - path=self.object_path, interface=cs.CONN, - method='GetInterfaces') + + if implement_get_interfaces: + q.add_dbus_method_impl(self.GetInterfaces, + path=self.object_path, interface=cs.CONN, + method='GetInterfaces') + q.add_dbus_method_impl(self.InspectHandles, path=self.object_path, interface=cs.CONN, method='InspectHandles') @@ -254,6 +258,13 @@ class SimulatedChannel(object): assert not self.announced self.announced = True self.conn.channels.append(self) + self.q.dbus_emit(self.conn.object_path, cs.CONN, + 'NewChannel', + self.object_path, self.immutable[cs.CHANNEL + '.ChannelType'], + self.immutable.get(cs.CHANNEL + '.TargetHandleType', 0), + self.immutable.get(cs.CHANNEL + '.TargetHandle', 0), + self.immutable.get(cs.CHANNEL + '.Requested', False), + signature='osuub') self.q.dbus_emit(self.conn.object_path, cs.CONN_IFACE_REQUESTS, 'NewChannels', [(self.object_path, self.immutable)], signature='a(oa{sv})') |