From ea9c157612bbe02de331b01c70f8205efc765f38 Mon Sep 17 00:00:00 2001 From: Damian Minkov Date: Thu, 12 Feb 2015 12:10:04 +0200 Subject: [PATCH] Sends the initial xmpp presence packet after requesting and dispatching the roster. --- ...rationSetPersistentPresenceJabberImpl.java | 32 ++++-- .../ProtocolProviderServiceJabberImpl.java | 6 + .../ServerStoredContactListJabberImpl.java | 105 +++++++++++++++++- 3 files changed, 132 insertions(+), 11 deletions(-) diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetPersistentPresenceJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetPersistentPresenceJabberImpl.java index 4b5f420ff..84a7ce6b8 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetPersistentPresenceJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetPersistentPresenceJabberImpl.java @@ -582,6 +582,22 @@ public void publishPresenceStatus(PresenceStatus status, throw new IllegalArgumentException(status + " is not a valid Jabber status"); + // if we got publish presence and we are still in a process of + // initializing the roster, just save the status and we will dispatch + // it when we are ready with the roster as sending initial presence + // is recommended to be done after requesting the roster, but we want + // to also dispatch it + synchronized(ssContactList.getRosterInitLock()) + { + if(!ssContactList.isRosterInitialized()) + { + // store it + ssContactList.setInitialStatus(status); + ssContactList.setInitialStatusMessage(statusMessage); + return; + } + } + if (status.equals(jabberStatusEnum.getStatus(JabberStatusEnum.OFFLINE))) { parentProvider.unregister(); @@ -1050,13 +1066,13 @@ public void registrationStateChanged(RegistrationStateChangeEvent evt) // note that our listener will be added just before the // one used in the Roster itself, but later we // will wait for it to be ready - // (inside method XMPPConnection.getRoaster()) + // (inside method XMPPConnection.getRoster()) parentProvider.getConnection().addPacketListener( new ServerStoredListInit(), new PacketTypeFilter(RosterPacket.class) ); - // will be used to store presence events till roaster is + // will be used to store presence events till roster is // initialized contactChangesListener = new ContactChangesListener(); @@ -1799,9 +1815,9 @@ else if(response.getResponseCode() /** * Runnable that resolves our list against the server side roster. - * This thread is the one which will call getRoaster for the first time. - * And if roaster is currently processing will wait for it (the wait - * is internal into XMPPConnection.getRoaster method). + * This thread is the one which will call getRoster for the first time. + * And if roster is currently processing will wait for it (the wait + * is internal into XMPPConnection.getRoster method). */ private class ServerStoredListInit implements Runnable, @@ -1817,16 +1833,16 @@ public void run() // init ssList ssContactList.init(contactChangesListener); - // as we have dispatched the contact list and Roaster is ready + // as we have dispatched the contact list and Roster is ready // lets start the jingle nodes discovery parentProvider.startJingleNodesDiscovery(); } /** - * When roaster packet with no error is received we are ready to + * When roster packet with no error is received we are ready to * to dispatch the contact list, doing it in different thread * to avoid blocking xmpp packet receiving. - * @param packet the roaster packet + * @param packet the roster packet */ public void processPacket(Packet packet) { diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java index 095075e2f..ef4eb6fdb 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java @@ -1153,6 +1153,12 @@ private ConnectState connectAndLogin( serviceName, proxy ); + // if we have OperationSetPersistentPresence skip sending initial + // presence while login is executed, the OperationSet will take care + // of it + if(getOperationSet(OperationSetPersistentPresence.class) != null) + confConn.setSendPresence(false); + confConn.setReconnectionAllowed(false); boolean tlsRequired = loginStrategy.isTlsRequired(); diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ServerStoredContactListJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ServerStoredContactListJabberImpl.java index 7c5f3a5d3..9d35f6b64 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ServerStoredContactListJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ServerStoredContactListJabberImpl.java @@ -83,6 +83,26 @@ public class ServerStoredContactListJabberImpl */ private InfoRetreiver infoRetreiver = null; + /** + * Whether roster has been requested and dispatched. + */ + private boolean isRosterInitialized = false; + + /** + * Lock object for the isRosterInitialized variable. + */ + private Object rosterInitLock = new Object(); + + /** + * The initial status saved. + */ + private PresenceStatus initialStatus = null; + + /** + * The initial status message saved. + */ + private String initialStatusMessage = null; + /** * Creates a ServerStoredContactList wrapper for the specified BuddyList. * @@ -868,12 +888,50 @@ void init(OperationSetPersistentPresenceJabberImpl.ContactChangesListener this.roster.setSubscriptionMode(Roster.SubscriptionMode.manual); initRoster(); + + // roster has been requested and dispatched, mark this + synchronized(rosterInitLock) + { + this.isRosterInitialized = true; + } + // no send initial status + sendInitialStatus(); + presenceChangeListener.processStoredEvents(); rosterChangeListener = new ChangeListener(); this.roster.addRosterListener(rosterChangeListener); } + /** + * Sends the initial presence to server. RFC 6121 says: + * a client SHOULD request the roster before sending initial presence + * We extend this and send it after we have dispatched the roster + */ + void sendInitialStatus() + { + // if we have initial status saved use it + if(initialStatus != null) + { + try + { + parentOperationSet.publishPresenceStatus( + initialStatus, initialStatusMessage); + } + catch(OperationFailedException ex) + { + logger.error("Error publishing initial presence", ex); + } + } + else + getParentProvider().getConnection() + .sendPacket(new Presence(Presence.Type.available)); + + // clean + initialStatus = null; + initialStatusMessage = null; + } + /** * Cleanups references and listeners. */ @@ -890,6 +948,11 @@ void cleanup() this.rosterChangeListener = null; this.roster = null; + + synchronized(rosterInitLock) + { + this.isRosterInitialized = false; + } } /** @@ -954,11 +1017,11 @@ private synchronized void initRoster() try { // process status if any that was received - // while the roaster reply packet was received and + // while the roster reply packet was received and // added our presence listener // Fixes a problem where Presence packets can be received - // before the roaster items packet, and we miss it, - // cause we add our listener after roaster is received + // before the roster items packet, and we miss it, + // cause we add our listener after roster is received // and smack don't allow to add our listener earlier parentOperationSet.firePresenceStatusChanged( roster.getPresence(item.getUser())); @@ -1902,4 +1965,40 @@ public Iterator getPresences(String user) { return roster.getPresences(user); } + + /** + * Returns whether roster is initialized. + * @return whether roster is initialized. + */ + boolean isRosterInitialized() + { + return isRosterInitialized; + } + + /** + * The lock around isRosterInitialized variable. + * @return the lock around isRosterInitialized variable. + */ + Object getRosterInitLock() + { + return rosterInitLock; + } + + /** + * Saves the initial status for later dispatching. + * @param initialStatus to be dispatched later. + */ + void setInitialStatus(PresenceStatus initialStatus) + { + this.initialStatus = initialStatus; + } + + /** + * Saves the initial status message for later dispatching. + * @param initialStatusMessage to be dispatched later. + */ + void setInitialStatusMessage(String initialStatusMessage) + { + this.initialStatusMessage = initialStatusMessage; + } }