diff options
-rw-r--r-- | src/conn-olpc.c | 4 | ||||
-rw-r--r-- | src/im-factory.c | 28 | ||||
-rw-r--r-- | src/muc-channel.c | 93 | ||||
-rw-r--r-- | src/muc-channel.h | 7 | ||||
-rw-r--r-- | src/muc-factory.c | 96 | ||||
-rw-r--r-- | tests/twisted/jingle/call-muc-re-re-request.py | 16 | ||||
-rw-r--r-- | tests/twisted/muc/only-text-when-needed.py | 416 | ||||
-rw-r--r-- | tests/twisted/servicetest.py | 6 | ||||
-rw-r--r-- | tests/twisted/tubes/offer-muc-dbus-tube.py | 57 | ||||
-rw-r--r-- | tests/twisted/tubes/test-get-available-tubes.py | 5 |
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')) |