summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/conn-olpc.c4
-rw-r--r--src/im-factory.c28
-rw-r--r--src/muc-channel.c93
-rw-r--r--src/muc-channel.h7
-rw-r--r--src/muc-factory.c96
-rw-r--r--tests/twisted/jingle/call-muc-re-re-request.py16
-rw-r--r--tests/twisted/muc/only-text-when-needed.py416
-rw-r--r--tests/twisted/servicetest.py6
-rw-r--r--tests/twisted/tubes/offer-muc-dbus-tube.py57
-rw-r--r--tests/twisted/tubes/test-get-available-tubes.py5
10 files changed, 652 insertions, 76 deletions
diff --git a/src/conn-olpc.c b/src/conn-olpc.c
index 080b95902..4dec11bb7 100644
--- a/src/conn-olpc.c
+++ b/src/conn-olpc.c
@@ -2628,6 +2628,10 @@ muc_channel_closed_cb (GabbleMucChannel *chan,
TpHandleSet *my_activities;
gboolean was_in_our_pep = FALSE;
+ /* is the muc channel /actually/ disappearing */
+ if (!tp_base_channel_is_destroyed (TP_BASE_CHANNEL (chan)))
+ return;
+
g_object_get (activity, "connection", &conn, NULL);
/* Revoke invitations we sent for this activity */
diff --git a/src/im-factory.c b/src/im-factory.c
index 72c33956d..08b02a619 100644
--- a/src/im-factory.c
+++ b/src/im-factory.c
@@ -281,35 +281,39 @@ im_channel_closed_cb (GabbleIMChannel *chan, gpointer user_data)
{
GabbleImFactory *self = GABBLE_IM_FACTORY (user_data);
GabbleImFactoryPrivate *priv = self->priv;
- TpHandle contact_handle;
- gboolean really_destroyed;
+ TpBaseChannel *base = TP_BASE_CHANNEL (chan);
+ TpHandle contact_handle = tp_base_channel_get_target_handle (base);
DEBUG ("%p, channel %p", self, chan);
- tp_channel_manager_emit_channel_closed_for_object (self,
- (TpExportableChannel *) chan);
+ if (tp_base_channel_is_registered (base))
+ {
+ tp_channel_manager_emit_channel_closed_for_object (self,
+ (TpExportableChannel *) chan);
+ }
if (priv->channels != NULL)
{
- g_object_get (chan,
- "handle", &contact_handle,
- "channel-destroyed", &really_destroyed,
- NULL);
-
- if (really_destroyed)
+ if (tp_base_channel_is_destroyed (base))
{
DEBUG ("removing channel with handle %u", contact_handle);
g_hash_table_remove (priv->channels,
GUINT_TO_POINTER (contact_handle));
}
- else
+ else if (tp_base_channel_is_respawning (base))
{
-
DEBUG ("reopening channel with handle %u due to pending messages",
contact_handle);
tp_channel_manager_emit_new_channel (self,
(TpExportableChannel *) chan, NULL);
}
+ else
+ {
+ /* this basically means tp_base_channel_disappear() must
+ * have been called; this doesn't have any meaning in this
+ * channel manager. */
+ g_assert_not_reached ();
+ }
}
}
diff --git a/src/muc-channel.c b/src/muc-channel.c
index 4e81ee1c2..cb675f287 100644
--- a/src/muc-channel.c
+++ b/src/muc-channel.c
@@ -125,6 +125,8 @@ enum
NEW_CALL,
#endif
+ APPEARED,
+
LAST_SIGNAL
};
@@ -134,6 +136,7 @@ static guint signals[LAST_SIGNAL] = {0};
enum
{
PROP_STATE = 1,
+ PROP_INITIALLY_REGISTER,
PROP_INVITED,
PROP_INVITATION_MESSAGE,
PROP_SELF_JID,
@@ -167,6 +170,8 @@ struct _GabbleMucChannelPrivate
{
GabbleMucState state;
gboolean closing;
+ gboolean autoclose;
+ gboolean initially_register;
guint join_timer_id;
guint poll_timer_id;
@@ -423,7 +428,8 @@ gabble_muc_channel_constructed (GObject *obj)
}
/* register object on the bus */
- tp_base_channel_register (base);
+ if (priv->initially_register)
+ tp_base_channel_register (base);
/* initialize group mixin */
tp_group_mixin_init (obj,
@@ -873,6 +879,9 @@ gabble_muc_channel_get_property (GObject *object,
case PROP_STATE:
g_value_set_uint (value, priv->state);
break;
+ case PROP_INITIALLY_REGISTER:
+ g_value_set_boolean (value, priv->initially_register);
+ break;
case PROP_SELF_JID:
g_value_set_string (value, priv->self_jid->str);
break;
@@ -945,6 +954,9 @@ gabble_muc_channel_set_property (GObject *object,
channel_state_changed (chan, prev_state, priv->state);
break;
+ case PROP_INITIALLY_REGISTER:
+ priv->initially_register = g_value_get_boolean (value);
+ break;
case PROP_INVITED:
priv->invited = g_value_get_boolean (value);
break;
@@ -1071,6 +1083,12 @@ gabble_muc_channel_class_init (GabbleMucChannelClass *gabble_muc_channel_class)
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_STATE, param_spec);
+ param_spec = g_param_spec_boolean ("initially-register", "Initially register",
+ "whether to register the channel on the bus on creation",
+ TRUE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIALLY_REGISTER, param_spec);
+
param_spec = g_param_spec_boolean ("invited", "Invited?",
"Whether the user has been invited to the channel.", FALSE,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
@@ -1226,6 +1244,14 @@ gabble_muc_channel_class_init (GabbleMucChannelClass *gabble_muc_channel_class)
* wants a value type, not an interface. */
G_TYPE_NONE, 1, TP_TYPE_BASE_CHANNEL);
+ signals[APPEARED] = g_signal_new ("appeared",
+ G_OBJECT_CLASS_TYPE (gabble_muc_channel_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
#ifdef ENABLE_VOIP
signals[NEW_CALL] = g_signal_new ("new-call",
G_OBJECT_CLASS_TYPE (gabble_muc_channel_class),
@@ -1525,9 +1551,32 @@ close_channel (GabbleMucChannel *chan, const gchar *reason,
GError error = { TP_ERROR, TP_ERROR_CANCELLED,
"Muc channel closed below us" };
- if (tp_base_channel_is_destroyed (base) || priv->closing)
+ if (tp_base_channel_is_destroyed (base))
return;
+ /* if priv->closing is TRUE, we're waiting for the MUC to echo our
+ * presence. however, if we're being asked to close again, but this
+ * time without letting the muc know, let's actually close. if we
+ * don't then the channel won't disappear from the bus properly. */
+ if (priv->closing && !inform_muc)
+ {
+ clear_leave_timer (chan);
+ tp_base_channel_destroyed (base);
+ return;
+ }
+
+ /* If inform_muc is TRUE it means that we're closing the channel
+ * gracefully and we don't mind if the channel doesn't actually
+ * close behind the scenes if a tube/call is still open. Every call
+ * to this function has inform_muc=FALSE, except for Channel.Close()
+ * and RemoveMembers(self_handle) */
+ if (inform_muc && !gabble_muc_channel_can_be_closed (chan))
+ {
+ priv->autoclose = TRUE;
+ tp_base_channel_disappear (base);
+ return;
+ }
+
DEBUG ("Closing");
/* Ensure we stay alive even while telling everyone else to abandon us. */
g_object_ref (chan);
@@ -1591,6 +1640,35 @@ _gabble_muc_channel_is_ready (GabbleMucChannel *chan)
return priv->ready;
}
+/* returns TRUE if there are no tube or Call channels open in this MUC */
+gboolean
+gabble_muc_channel_can_be_closed (GabbleMucChannel *chan)
+{
+ GabbleMucChannelPrivate *priv = chan->priv;
+
+ if (g_hash_table_size (priv->tubes) > 0)
+ return FALSE;
+
+ if (priv->calls != NULL || priv->call_requests != NULL
+ || priv->call_initiating)
+ return FALSE;
+
+ return TRUE;
+}
+
+gboolean
+gabble_muc_channel_get_autoclose (GabbleMucChannel *chan)
+{
+ return chan->priv->autoclose;
+}
+
+void
+gabble_muc_channel_set_autoclose (GabbleMucChannel *chan,
+ gboolean autoclose)
+{
+ chan->priv->autoclose = autoclose;
+}
+
static gboolean
handle_nick_conflict (GabbleMucChannel *chan,
WockyStanza *stanza,
@@ -2979,6 +3057,17 @@ _gabble_muc_channel_receive (GabbleMucChannel *chan,
return;
}
+ /* are we actually hidden? */
+ if (!tp_base_channel_is_registered (base))
+ {
+ DEBUG ("making MUC channel reappear!");
+ tp_base_channel_reopened_with_requested (base, FALSE, sender);
+ g_signal_emit (chan, signals[APPEARED], 0);
+ }
+
+ /* let's not autoclose now */
+ chan->priv->autoclose = FALSE;
+
message = tp_cm_message_new (base_conn, 2);
/* Header common to normal message and delivery-echo */
diff --git a/src/muc-channel.h b/src/muc-channel.h
index 5cd560563..2aaa95cc7 100644
--- a/src/muc-channel.h
+++ b/src/muc-channel.h
@@ -88,6 +88,13 @@ GType gabble_muc_channel_get_type (void);
gboolean _gabble_muc_channel_is_ready (GabbleMucChannel *chan);
+void gabble_muc_channel_set_autoclose (GabbleMucChannel *chan,
+ gboolean autoclose);
+
+gboolean gabble_muc_channel_get_autoclose (GabbleMucChannel *chan);
+
+gboolean gabble_muc_channel_can_be_closed (GabbleMucChannel *chan);
+
void gabble_muc_channel_send_presence (GabbleMucChannel *chan);
gboolean gabble_muc_channel_send_invite (GabbleMucChannel *self,
diff --git a/src/muc-factory.c b/src/muc-factory.c
index 2911b34a1..60f5f2ec7 100644
--- a/src/muc-factory.c
+++ b/src/muc-factory.c
@@ -234,12 +234,17 @@ muc_channel_closed_cb (GabbleMucChannel *chan, gpointer user_data)
{
GabbleMucFactory *fac = GABBLE_MUC_FACTORY (user_data);
GabbleMucFactoryPrivate *priv = fac->priv;
+ TpBaseChannel *base = TP_BASE_CHANNEL (chan);
TpHandle room_handle;
- tp_channel_manager_emit_channel_closed_for_object (fac,
- TP_EXPORTABLE_CHANNEL (chan));
+ if (tp_base_channel_is_registered (base))
+ {
+ tp_channel_manager_emit_channel_closed_for_object (fac,
+ TP_EXPORTABLE_CHANNEL (chan));
+ }
- if (priv->text_channels != NULL)
+ if (tp_base_channel_is_destroyed (base)
+ && priv->text_channels != NULL)
{
g_object_get (chan, "handle", &room_handle, NULL);
@@ -250,12 +255,23 @@ muc_channel_closed_cb (GabbleMucChannel *chan, gpointer user_data)
}
static void
+muc_channel_appeared_cb (GabbleMucChannel *chan,
+ gpointer user_data)
+{
+ GabbleMucFactory *fac = GABBLE_MUC_FACTORY (user_data);
+
+ tp_channel_manager_emit_new_channel (fac,
+ TP_EXPORTABLE_CHANNEL (chan), NULL);
+}
+
+static void
muc_ready_cb (GabbleMucChannel *text_chan,
gpointer data)
{
GabbleMucFactory *fac = GABBLE_MUC_FACTORY (data);
GabbleMucFactoryPrivate *priv = fac->priv;
GHashTable *channels;
+ TpBaseChannel *base = TP_BASE_CHANNEL (text_chan);
GSList *requests_satisfied_text = NULL;
GQueue *tube_channels;
@@ -292,8 +308,14 @@ muc_ready_cb (GabbleMucChannel *text_chan,
g_hash_table_remove (priv->text_needed_for_tube, text_chan);
}
- /* finally add the text channel */
- g_hash_table_insert (channels, text_chan, requests_satisfied_text);
+ /* only announce channels which are on the bus (requested or
+ * requested with an invite, not channels only around because they
+ * have to be) */
+ if (tp_base_channel_is_registered (base))
+ {
+ tp_channel_manager_emit_new_channel (fac,
+ TP_EXPORTABLE_CHANNEL (text_chan), requests_satisfied_text);
+ }
tp_channel_manager_emit_new_channels (fac, channels);
@@ -359,9 +381,23 @@ muc_sub_channel_closed_cb (TpSvcChannel *chan,
gpointer user_data)
{
GabbleMucFactory *fac = GABBLE_MUC_FACTORY (user_data);
+ GabbleMucChannel *muc;
tp_channel_manager_emit_channel_closed_for_object (fac,
TP_EXPORTABLE_CHANNEL (chan));
+
+ /* GabbleTubeDBus, GabbleTubeStream, and GabbleMucCallChannel all
+ * have "muc" properties. */
+ g_object_get (chan,
+ "muc", &muc,
+ NULL);
+
+ if (muc == NULL)
+ return;
+
+ if (gabble_muc_channel_can_be_closed (muc)
+ && gabble_muc_channel_get_autoclose (muc))
+ tp_base_channel_close (TP_BASE_CHANNEL (muc));
}
#ifdef ENABLE_VOIP
@@ -412,6 +448,7 @@ new_muc_channel (GabbleMucFactory *fac,
TpHandle inviter,
const gchar *message,
gboolean requested,
+ gboolean needed_not_wanted,
GHashTable *initial_channels,
GArray *initial_handles,
char **initial_ids,
@@ -461,8 +498,10 @@ new_muc_channel (GabbleMucFactory *fac,
"initial-invitee-handles", initial_handles,
"initial-invitee-ids", initial_ids,
"room-name", room_name,
+ "initially-register", !needed_not_wanted,
NULL);
+ g_signal_connect (chan, "appeared", (GCallback) muc_channel_appeared_cb, fac);
g_signal_connect (chan, "closed", (GCallback) muc_channel_closed_cb, fac);
g_signal_connect (chan, "new-tube", (GCallback) muc_channel_new_tube, fac);
#ifdef ENABLE_VOIP
@@ -510,8 +549,8 @@ do_invite (GabbleMucFactory *fac,
if (g_hash_table_lookup (priv->text_channels,
GUINT_TO_POINTER (room_handle)) == NULL)
{
- new_muc_channel (fac, room_handle, TRUE, inviter_handle, reason, FALSE,
- NULL, NULL, NULL, NULL);
+ new_muc_channel (fac, room_handle, TRUE, inviter_handle, reason,
+ FALSE, FALSE, NULL, NULL, NULL, NULL);
}
else
{
@@ -988,6 +1027,7 @@ ensure_muc_channel (GabbleMucFactory *fac,
TpHandle handle,
GabbleMucChannel **ret,
gboolean requested,
+ gboolean needed_not_wanted,
GHashTable *initial_channels,
GArray *initial_handles,
char **initial_ids,
@@ -999,9 +1039,17 @@ ensure_muc_channel (GabbleMucFactory *fac,
if (*ret == NULL)
{
- *ret = new_muc_channel (fac, handle, FALSE, base_conn->self_handle, NULL,
- requested, initial_channels, initial_handles, initial_ids, room_name);
- return FALSE;
+ *ret = new_muc_channel (fac, handle, FALSE, base_conn->self_handle,
+ NULL, requested, needed_not_wanted, initial_channels,
+ initial_handles, initial_ids, room_name);
+
+ gabble_muc_channel_set_autoclose (*ret, needed_not_wanted);
+ }
+ else
+ {
+ /* only set this if it's already enabled */
+ if (gabble_muc_channel_get_autoclose (*ret))
+ gabble_muc_channel_set_autoclose (*ret, needed_not_wanted);
}
if (_gabble_muc_channel_is_ready (*ret))
@@ -1390,12 +1438,13 @@ handle_text_channel_request (GabbleMucFactory *self,
}
- if (ensure_muc_channel (self, priv, room, &text_chan, TRUE,
+ if (ensure_muc_channel (self, priv, room, &text_chan, TRUE, FALSE,
final_channels, final_handles, final_ids, room_name))
{
/* channel exists */
- if (require_new)
+ if (require_new
+ && tp_base_channel_is_registered (TP_BASE_CHANNEL (text_chan)))
{
g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
"That channel has already been created (or requested)");
@@ -1414,8 +1463,23 @@ handle_text_channel_request (GabbleMucFactory *self,
}
else
{
- tp_channel_manager_emit_request_already_satisfied (self,
- request_token, TP_EXPORTABLE_CHANNEL (text_chan));
+ if (tp_base_channel_is_registered (TP_BASE_CHANNEL (text_chan)))
+ {
+ tp_channel_manager_emit_request_already_satisfied (self,
+ request_token, TP_EXPORTABLE_CHANNEL (text_chan));
+ }
+ else
+ {
+ GSList *tokens;
+
+ tp_base_channel_register (TP_BASE_CHANNEL (text_chan));
+
+ tokens = g_slist_append (NULL, request_token);
+ tp_channel_manager_emit_new_channel (self,
+ TP_EXPORTABLE_CHANNEL (text_chan), tokens);
+ g_slist_free (tokens);
+ }
+
ret = TRUE;
}
}
@@ -1481,7 +1545,7 @@ handle_tube_channel_request (GabbleMucFactory *self,
gmuc = g_hash_table_lookup (priv->text_channels, GUINT_TO_POINTER (handle));
if (gmuc == NULL)
- ensure_muc_channel (self, priv, handle, &gmuc, FALSE,
+ ensure_muc_channel (self, priv, handle, &gmuc, FALSE, TRUE,
NULL, NULL, NULL, NULL);
can_announce_now = _gabble_muc_channel_is_ready (gmuc);
@@ -1637,7 +1701,7 @@ handle_call_channel_request (GabbleMucFactory *self,
return FALSE;
}
- ensure_muc_channel (self, priv, handle, &muc, FALSE, NULL, NULL, NULL, NULL);
+ ensure_muc_channel (self, priv, handle, &muc, FALSE, TRUE, NULL, NULL, NULL, NULL);
call = gabble_muc_channel_get_call (muc);
diff --git a/tests/twisted/jingle/call-muc-re-re-request.py b/tests/twisted/jingle/call-muc-re-re-request.py
index 087c63f2c..1f72db4c8 100644
--- a/tests/twisted/jingle/call-muc-re-re-request.py
+++ b/tests/twisted/jingle/call-muc-re-re-request.py
@@ -36,16 +36,20 @@ def run_cancel_test(q, bus, conn, stream):
# Accept the channel
channel.Accept()
- e = q.expect('stream-presence', to = muc + "/test")
- mujinode = xpath.queryForNodes("/presence/muji/preparing", e.stanza)
- assertNotEquals(None, mujinode)
+ def preparing(e):
+ node = xpath.queryForNodes("/presence/muji/preparing", e.stanza)
+ return node is not None
+
+ q.expect('stream-presence', to = muc + "/test", predicate=preparing)
channel.Hangup(0, "", "",
dbus_interface=cs.CHANNEL_TYPE_CALL)
- e = q.expect('stream-presence', to = muc + "/test")
- mujinode = xpath.queryForNodes("/presence/muji/preparing", e.stanza)
- assertEquals(None, mujinode)
+ def notpreparing(e):
+ node = xpath.queryForNodes("/presence/muji/preparing", e.stanza)
+ return node is None
+
+ q.expect('stream-presence', to = muc + "/test", predicate=notpreparing)
if x % 2 == 0:
channel.Close()
diff --git a/tests/twisted/muc/only-text-when-needed.py b/tests/twisted/muc/only-text-when-needed.py
new file mode 100644
index 000000000..260d26c8a
--- /dev/null
+++ b/tests/twisted/muc/only-text-when-needed.py
@@ -0,0 +1,416 @@
+"""
+Test support for creating MUC text channels when necessary, not all
+the time.
+"""
+
+import dbus
+
+from servicetest import call_async, EventPattern, assertEquals, \
+ sync_dbus, wrap_channel
+from gabbletest import exec_test, acknowledge_iq, make_muc_presence, \
+ sync_stream, elem
+import constants as cs
+import ns
+
+from mucutil import echo_muc_presence
+
+def request_stream_tube(q, bus, conn, method, jid):
+ call_async(q, conn.Requests, method,
+ { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_STREAM_TUBE,
+ cs.TARGET_HANDLE_TYPE: cs.HT_ROOM,
+ cs.TARGET_ID: jid,
+ cs.STREAM_TUBE_SERVICE: 'the.service',
+ })
+
+def stream_tube(q, bus, conn, stream, method, jid, presence=True):
+ request_stream_tube(q, bus, conn, method, jid)
+ if presence:
+ send_muc_presence(q, stream, jid)
+ e, _ = q.expect_many(EventPattern('dbus-return', method=method),
+ EventPattern('dbus-signal', signal='NewChannels'))
+
+ # sigh
+ if method == 'EnsureChannel':
+ path = e.value[1]
+ else:
+ path = e.value[0]
+
+ tube_chan = wrap_channel(bus.get_object(conn.bus_name, path), 'StreamTube')
+
+ return (tube_chan,) + e.value
+
+def request_text_channel(q, bus, conn, method, jid):
+ call_async(q, conn.Requests, method,
+ { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT,
+ cs.TARGET_HANDLE_TYPE: cs.HT_ROOM,
+ cs.TARGET_ID: jid,
+ })
+
+def text_channel(q, bus, conn, stream, method, jid, presence=True):
+ request_text_channel(q, bus, conn, method, jid)
+ if presence:
+ send_muc_presence(q, stream, jid)
+ e, _ = q.expect_many(EventPattern('dbus-return', method=method),
+ EventPattern('dbus-signal', signal='NewChannels'))
+
+ # sigh
+ if method == 'EnsureChannel':
+ path = e.value[1]
+ else:
+ path = e.value[0]
+
+ text_chan = wrap_channel(bus.get_object(conn.bus_name, path), 'Text')
+
+ return (text_chan,) + e.value
+
+def send_muc_presence(q, stream, jid):
+ q.expect('stream-presence', to='%s/test' % jid)
+
+ stream.send(make_muc_presence('owner', 'moderator', jid, 'bob'))
+ stream.send(make_muc_presence('none', 'participant', jid, 'test'))
+
+def expect_close(q, path, stream=None, jid=None):
+ forbid_unavailable = [EventPattern('stream-presence',
+ presence_type='unavailable',
+ to='%s/test' % jid)]
+
+ if jid is not None:
+ e = q.expect_many(*forbid_unavailable)[0]
+ echo_muc_presence(q, stream, e.stanza, 'none', 'participant')
+ else:
+ q.forbid_events(forbid_unavailable)
+
+ q.expect_many(EventPattern('dbus-signal', signal='ChannelClosed',
+ args=[path]),
+ EventPattern('dbus-signal', signal='Closed',
+ path=path))
+
+ if jid is not None:
+ q.unforbid_events(forbid_unavailable)
+
+def assert_on_bus(q, chan):
+ call_async(q, chan.Properties, 'GetAll', cs.CHANNEL)
+ e = q.expect('dbus-return', method='GetAll')
+ props = e.value[0]
+ assert 'ChannelType' in props
+
+def assert_not_on_bus(q, chan):
+ call_async(q, chan.Properties, 'GetAll', cs.CHANNEL)
+ q.expect('dbus-error', method='GetAll',
+ name='org.freedesktop.DBus.Error.UnknownMethod')
+
+# tests start here
+
+def tube_no_text(q, bus, conn, stream):
+ jid = 'test@conf.localhost'
+
+ # create a stream tube.
+ # this will need a MUC channel to be opened, but we want to make
+ # sure it doesn't get signalled.
+ request_stream_tube(q, bus, conn, 'CreateChannel', jid)
+
+ send_muc_presence(q, stream, jid)
+
+ ret, new_sig = q.expect_many(
+ EventPattern('dbus-return', method='CreateChannel'),
+ EventPattern('dbus-signal', signal='NewChannels'))
+
+ q.forbid_events([EventPattern('dbus-signal', signal='NewChannels')])
+
+ tube_path, tube_props = ret.value
+ assertEquals(cs.CHANNEL_TYPE_STREAM_TUBE, tube_props[cs.CHANNEL_TYPE])
+
+ channels = new_sig.args[0]
+ assertEquals(1, len(channels))
+ path, props = channels[0]
+
+ assertEquals(tube_path, path)
+ assertEquals(tube_props, props)
+
+ sync_dbus(bus, q, conn)
+
+ q.unforbid_all()
+
+def tube_then_text(q, bus, conn, stream):
+ jid = 'test@conf.localhost'
+
+ # first let's get a stream tube
+ stream_tube(q, bus, conn, stream, 'CreateChannel', jid)
+
+ # now let's try and ensure the text channel which should happen
+ # immediately
+ request_text_channel(q, bus, conn, 'EnsureChannel', jid)
+
+ ret = q.expect('dbus-return', method='EnsureChannel')
+
+ yours, text_path, text_props = ret.value
+ assertEquals(True, yours)
+ assertEquals(cs.CHANNEL_TYPE_TEXT, text_props[cs.CHANNEL_TYPE])
+
+ new_sig = q.expect('dbus-signal', signal='NewChannels')
+
+ channels = new_sig.args[0]
+ assertEquals(1, len(channels))
+ path, props = channels[0]
+
+ assertEquals(text_path, path)
+ assertEquals(text_props, props)
+
+def tube_remains_text_closes(q, bus, conn, stream):
+ jid = 'test@conf.localhost'
+
+ text_chan, text_path, _ = text_channel(q, bus, conn, stream, 'CreateChannel', jid)
+ tube_chan, tube_path, _ = stream_tube(q, bus, conn, stream, 'CreateChannel', jid,
+ presence=False)
+
+ # now let's try and close the text channel
+ # this should happen sucessfully but the tube channel
+ # should stick around
+ q.forbid_events([EventPattern('dbus-signal', signal='ChannelClosed',
+ args=[tube_path])])
+
+ text_chan.Close()
+ expect_close(q, text_path)
+
+ sync_dbus(bus, q, conn)
+
+ assert_on_bus(q, tube_chan)
+ assert_not_on_bus(q, text_chan)
+
+ q.unforbid_all()
+
+def normally_close_text(q, bus, conn, stream):
+ jid = 'test@conf.localhost'
+
+ text_chan, text_path, _ = text_channel(q, bus, conn, stream, 'CreateChannel', jid)
+
+ sync_stream(q, stream)
+
+ text_chan.Close()
+ expect_close(q, text_path, stream, jid)
+
+ assert_not_on_bus(q, text_chan)
+
+def text_can_automatically_close(q, bus, conn, stream):
+ jid = 'test@conf.localhost'
+
+ tube_chan, tube_path, _ = stream_tube(q, bus, conn, stream, 'CreateChannel', jid)
+
+ sync_dbus(bus, q, conn)
+
+ tube_chan.Close()
+ expect_close(q, tube_path, stream, jid)
+
+ assert_not_on_bus(q, tube_chan)
+
+def text_remains_after_tube(q, bus, conn, stream):
+ jid = 'test@conf.localhost'
+
+ tube_chan, tube_path, _ = stream_tube(q, bus, conn, stream, 'CreateChannel', jid)
+ text_chan, text_path, _ = text_channel(q, bus, conn, stream, 'CreateChannel', jid,
+ presence=False)
+
+ sync_dbus(bus, q, conn)
+
+ tube_chan.Close()
+ expect_close(q, tube_path)
+
+ assert_not_on_bus(q, tube_chan)
+ assert_on_bus(q, text_chan)
+
+ call_async(q, text_chan.Properties, 'GetAll', cs.CHANNEL_TYPE_TEXT)
+ q.expect('dbus-return', method='GetAll')
+
+ text_chan.Close()
+ expect_close(q, text_path, stream, jid)
+
+ assert_not_on_bus(q, tube_chan)
+ assert_not_on_bus(q, text_chan)
+
+def recreate_text(q, bus, conn, stream):
+ jid = 'test@conf.localhost'
+
+ tube_chan, _, _ = stream_tube(q, bus, conn, stream, 'CreateChannel', jid)
+ text_chan, text_path, text_props = text_channel(q, bus, conn, stream,
+ 'CreateChannel', jid, presence=False)
+
+ text_chan.Close()
+ expect_close(q, text_path)
+
+ assert_on_bus(q, tube_chan)
+ assert_not_on_bus(q, text_chan)
+
+ # now let's try and create the same text channel and hope we get
+ # back the same channel
+ q.forbid_events([EventPattern('stream-presence', to='%s/test' % jid)])
+
+ request_text_channel(q, bus, conn, 'CreateChannel', jid)
+
+ ret = q.expect('dbus-return', method='CreateChannel')
+
+ path, props = ret.value
+ assertEquals(cs.CHANNEL_TYPE_TEXT, props[cs.CHANNEL_TYPE])
+
+ new_sig = q.expect('dbus-signal', signal='NewChannels')
+
+ channels = new_sig.args[0]
+ assertEquals(1, len(channels))
+
+ assertEquals(path, channels[0][0])
+ assertEquals(props, channels[0][1])
+
+ # the channel should be identical given it's the same MucChannel
+ assertEquals(text_path, path)
+ assertEquals(text_props, props)
+
+ assert_on_bus(q, tube_chan)
+ assert_on_bus(q, text_chan)
+
+ q.unforbid_all()
+
+def test_channels(q, bus, conn, stream):
+ jid = 'test@conf.localhost'
+
+ tube_chan, _, _ = stream_tube(q, bus, conn, stream, 'CreateChannel', jid)
+ text_chan, text_path, _ = text_channel(q, bus, conn, stream,'CreateChannel',
+ jid, presence=False)
+
+ text_chan.Close()
+ expect_close(q, text_path)
+
+ # the following are basically the same as assert_[not_]on_bus()
+ # but they look pretty.
+
+ # methods on the text channel should fail
+ call_async(q, text_chan.Properties, 'GetAll', cs.CHANNEL_TYPE_TEXT)
+ q.expect('dbus-error', method='GetAll')
+
+ # but methods on the tube should pass
+ call_async(q, tube_chan.Properties, 'GetAll', cs.CHANNEL_TYPE_STREAM_TUBE)
+ q.expect('dbus-return', method='GetAll')
+
+def test_message(q, bus, conn, stream):
+ jid = 'test@conf.localhost'
+
+ tube_chan, tube_path, _ = stream_tube(q, bus, conn, stream, 'CreateChannel', jid)
+
+ bob_jid = '%s/bob' % jid
+ bob_handle = conn.RequestHandles(cs.HT_CONTACT, [bob_jid])[0]
+
+ # now let's send a message
+ stream.send(
+ elem('message', from_=bob_jid, type='groupchat')(
+ elem('body')(
+ u'oh hey i didnt see you there'
+ ),
+ )
+ )
+
+ # the text channel appears!
+ e = q.expect('dbus-signal', signal='NewChannels')
+ channels = e.args[0]
+ assertEquals(1, len(channels))
+ path, props = channels[0]
+ assertEquals(cs.CHANNEL_TYPE_TEXT, props[cs.CHANNEL_TYPE])
+ # make sure we didn't request it
+ assertEquals(False, props[cs.REQUESTED])
+ assertEquals(bob_handle, props[cs.INITIATOR_HANDLE])
+
+ # and the message is then signalled
+ e = q.expect('dbus-signal', signal='MessageReceived', path=path)
+ parts = e.args[0]
+
+ header = parts[0]
+ assertEquals(bob_handle, header['message-sender'])
+ assertEquals(bob_jid, header['message-sender-id'])
+
+ body = parts[1]
+ assertEquals('oh hey i didnt see you there', body['content'])
+
+def test_requested_message(q, bus, conn, stream):
+ jid = 'test@conf.localhost'
+
+ self_handle = conn.Properties.Get(cs.CONN, 'SelfHandle')
+
+ text_chan, text_path, _ = text_channel(q, bus, conn, stream,'CreateChannel', jid)
+ tube_chan, tube_path, _ = stream_tube(q, bus, conn, stream, 'CreateChannel', jid,
+ presence=False)
+
+ bob_jid = '%s/bob' % jid
+ bob_handle = conn.RequestHandles(cs.HT_CONTACT, [bob_jid])[0]
+
+ # make sure it says we requested it
+ props = text_chan.Properties.GetAll(cs.CHANNEL)
+ assertEquals(True, props['Requested'])
+ assertEquals(self_handle, props['InitiatorHandle'])
+
+ text_chan.Close()
+ expect_close(q, text_path)
+
+ assert_on_bus(q, tube_chan)
+ assert_not_on_bus(q, text_chan)
+
+ # now let's send a message
+ stream.send(
+ elem('message', from_=bob_jid, type='groupchat')(
+ elem('body')(
+ u'hello again'
+ ),
+ )
+ )
+
+ e = q.expect('dbus-signal', signal='NewChannels')
+ channels = e.args[0]
+ assertEquals(1, len(channels))
+ path, props = channels[0]
+ assertEquals(cs.CHANNEL_TYPE_TEXT, props[cs.CHANNEL_TYPE])
+ # now make sure we didn't request it
+ assertEquals(False, props[cs.REQUESTED])
+ assertEquals(bob_handle, props[cs.INITIATOR_HANDLE])
+
+ e = q.expect('dbus-signal', signal='MessageReceived', path=path)
+ parts = e.args[0]
+
+ header = parts[0]
+ assertEquals(bob_handle, header['message-sender'])
+ assertEquals(bob_jid, header['message-sender-id'])
+
+ body = parts[1]
+ assertEquals('hello again', body['content'])
+
+ assert_on_bus(q, tube_chan)
+ assert_on_bus(q, text_chan)
+
+if __name__ == '__main__':
+ # request tube, assert no text appears
+ exec_test(tube_no_text)
+
+ # request tube, request text (no presence), assert both appear
+ exec_test(tube_then_text)
+
+ # request tube & text, close text, assert tube doesn't close
+ exec_test(tube_remains_text_closes)
+
+ # request text, close text, assert unavailable presence
+ exec_test(normally_close_text)
+
+ # request tube, close tube, assert unavailable presence
+ exec_test(text_can_automatically_close)
+
+ # request tube & text, close tube, assert text doesn't close
+ exec_test(text_remains_after_tube)
+
+ # request tube & text, close text, request text (no presence),
+ # assert appears as normal
+ exec_test(recreate_text)
+
+ # request tube & text, close text, assert GetAll on text fails but
+ # works on tube
+ exec_test(test_channels)
+
+ # request tube, incoming message, assert text channel appears
+ exec_test(test_message)
+
+ # request text & tube, close text, incoming message, assert text
+ # reappears with correct props
+ exec_test(test_requested_message)
diff --git a/tests/twisted/servicetest.py b/tests/twisted/servicetest.py
index 6468fda32..31c00f476 100644
--- a/tests/twisted/servicetest.py
+++ b/tests/twisted/servicetest.py
@@ -155,6 +155,12 @@ class BaseEventQueue:
"""
self.forbidden_events.difference_update(set(patterns))
+ def unforbid_all(self):
+ """
+ Remove all patterns from the set of forbidden events.
+ """
+ self.forbidden_events.clear()
+
def _check_forbidden(self, event):
for e in self.forbidden_events:
if e.match(event):
diff --git a/tests/twisted/tubes/offer-muc-dbus-tube.py b/tests/twisted/tubes/offer-muc-dbus-tube.py
index 8332a866e..06ccc2804 100644
--- a/tests/twisted/tubes/offer-muc-dbus-tube.py
+++ b/tests/twisted/tubes/offer-muc-dbus-tube.py
@@ -127,18 +127,11 @@ def test(q, bus, conn, stream, access_control):
}
join_muc(q, bus, conn, stream, muc, request=request)
- def find_channel(channels, ctype):
- for path, props in channels:
- if props[cs.CHANNEL_TYPE] == ctype:
- return path, props
-
- return None, None
-
e = q.expect('dbus-signal', signal='NewChannels')
channels = e.args[0]
- assert len(channels) == 2
- path, prop = find_channel(channels, cs.CHANNEL_TYPE_DBUS_TUBE)
+ assert len(channels) == 1
+ path, prop = channels[0]
assert prop[cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_DBUS_TUBE
assert prop[cs.INITIATOR_ID] == 'chat2@conf.localhost/test'
assert prop[cs.REQUESTED] == True
@@ -161,18 +154,6 @@ def test(q, bus, conn, stream, access_control):
assert tube_props['State'] == cs.TUBE_CHANNEL_STATE_NOT_OFFERED
- # now check the text channel
- path, prop = find_channel(channels, cs.CHANNEL_TYPE_TEXT)
- assert prop[cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_TEXT
- assert prop[cs.INITIATOR_HANDLE] == self_handle
- assert prop[cs.INITIATOR_ID] == self_name
- assert cs.CHANNEL_IFACE_GROUP in prop[cs.INTERFACES]
- assert prop[cs.TARGET_ID] == 'chat2@conf.localhost'
- assert prop[cs.REQUESTED] == False
-
- text_chan = dbus.Interface(bus.get_object(conn.bus_name, path),
- cs.CHANNEL)
-
# try to offer using a wrong access control
try:
dbus_tube_iface.Offer(sample_parameters, cs.SOCKET_ACCESS_CONTROL_PORT)
@@ -262,26 +243,18 @@ def test(q, bus, conn, stream, access_control):
assert names == {tube_self_handle: my_bus_name}
chan_iface.Close()
- q.expect_many(
+ _, _, event = q.expect_many(
EventPattern('dbus-signal', signal='Closed'),
- EventPattern('dbus-signal', signal='ChannelClosed'))
-
- # leave the room
- text_chan.Close()
+ EventPattern('dbus-signal', signal='ChannelClosed'),
+ EventPattern('stream-presence', to='chat2@conf.localhost/test',
+ presence_type='unavailable'))
# we must echo the MUC presence so the room will actually close
# and we should wait to make sure gabble has actually parsed our
# echo before trying to rejoin
- event = q.expect('stream-presence', to='chat2@conf.localhost/test',
- presence_type='unavailable')
echo_muc_presence(q, stream, event.stanza, 'none', 'participant')
sync_stream(q, stream)
- q.expect_many(
- EventPattern('dbus-signal', signal='Closed'),
- EventPattern('dbus-signal', signal='ChannelClosed'),
- )
-
# rejoin the room
call_async(q, conn.Requests, 'CreateChannel',
{ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT,
@@ -296,17 +269,29 @@ def test(q, bus, conn, stream, access_control):
# Send presence for own membership of room.
stream.send(make_muc_presence('none', 'participant', muc, 'test'))
+ def new_tube(e):
+ path, props = e.args[0][0]
+ return props[cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_DBUS_TUBE
+
+ def new_text(e):
+ path, props = e.args[0][0]
+ return props[cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_TEXT
+
# tube and text is created
- e = q.expect('dbus-signal', signal='NewChannels')
+ text_event, tube_event = q.expect_many(EventPattern('dbus-signal', signal='NewChannels',
+ predicate=new_text),
+ EventPattern('dbus-signal', signal='NewChannels',
+ predicate=new_tube))
+
channels = e.args[0]
- tube_path, props = find_channel(channels, cs.CHANNEL_TYPE_DBUS_TUBE)
+ tube_path, props = tube_event.args[0][0]
assertEquals(cs.CHANNEL_TYPE_DBUS_TUBE, props[cs.CHANNEL_TYPE])
assertEquals('chat2@conf.localhost/test', props[cs.INITIATOR_ID])
assertEquals(False, props[cs.REQUESTED])
assertEquals(cs.HT_ROOM, props[cs.TARGET_HANDLE_TYPE])
assertEquals('com.example.TestCase', props[cs.DBUS_TUBE_SERVICE_NAME])
- _, props = find_channel(channels, cs.CHANNEL_TYPE_TEXT)
+ _, props = text_event.args[0][0]
assertEquals(cs.CHANNEL_TYPE_TEXT, props[cs.CHANNEL_TYPE])
assertEquals(True, props[cs.REQUESTED])
diff --git a/tests/twisted/tubes/test-get-available-tubes.py b/tests/twisted/tubes/test-get-available-tubes.py
index 23e806487..7b860ebfd 100644
--- a/tests/twisted/tubes/test-get-available-tubes.py
+++ b/tests/twisted/tubes/test-get-available-tubes.py
@@ -31,10 +31,7 @@ def test(q, bus, conn, stream):
cs.TARGET_ID: 'chat@conf.localhost',
cs.STREAM_TUBE_SERVICE: 'test'})
- stream_event = q.expect_many(
- EventPattern('dbus-signal', signal='MembersChanged',
- args=[u'', [], [], [], [2], 0, 0]),
- EventPattern('stream-presence', to='chat@conf.localhost/test'))
+ q.expect('stream-presence', to='chat@conf.localhost/test')
# Send presence for other member of room.
stream.send(make_muc_presence('owner', 'moderator', 'chat@conf.localhost', 'bob'))