summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon McVittie <simon.mcvittie@collabora.co.uk>2009-04-06 13:47:13 +0100
committerSimon McVittie <simon.mcvittie@collabora.co.uk>2009-04-06 13:47:13 +0100
commit8a892e9274d21f6303c7ed0fd47fe29185d2b7bd (patch)
tree9cf8188465b28ea37794ecdccb08663f4ecd479d
parented1f55a992a7884b29974682387ea9b363384d42 (diff)
parentc134cd4a6feef10c7b51b6aedd44d5f9ccfd189c (diff)
downloadtelepathy-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.h7
-rw-r--r--src/mcd-account-requests.c83
-rw-r--r--src/mcd-account-requests.h9
-rw-r--r--src/mcd-account.c16
-rw-r--r--src/mcd-channel.c254
-rw-r--r--src/mcd-channel.h20
-rw-r--r--src/mcd-connection.c7
-rw-r--r--src/mcd-dispatcher.c113
-rw-r--r--src/mcd-dispatcher.h6
-rw-r--r--test/twisted/Makefile.am3
-rw-r--r--test/twisted/account-requests/create-text.py23
-rw-r--r--test/twisted/account-requests/delete-account-during-request.py136
-rw-r--r--test/twisted/dispatcher/already-has-channel.py235
-rw-r--r--test/twisted/dispatcher/create-text.py184
-rw-r--r--test/twisted/dispatcher/dispatch-text.py2
-rw-r--r--test/twisted/mctest.py19
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})')