aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/java/sip
diff options
context:
space:
mode:
authorDanny van Heumen <danny@dannyvanheumen.nl>2015-02-13 21:21:01 +0100
committerDanny van Heumen <danny@dannyvanheumen.nl>2015-03-13 19:35:55 +0100
commit44b75018120003cab156e8ee162e1bfefd0229d3 (patch)
treef7ae7c6e9754ec3963bece40f3c74ff89f0f08f5 /src/net/java/sip
parent18178039e070918825a00678bceaa2fbb6ad5935 (diff)
downloadjitsi-44b75018120003cab156e8ee162e1bfefd0229d3.zip
jitsi-44b75018120003cab156e8ee162e1bfefd0229d3.tar.gz
jitsi-44b75018120003cab156e8ee162e1bfefd0229d3.tar.bz2
Added support for WATCH publish/subscribe mechanism for contact presence.
Now supports WATCH command for publish/subscribe to online presence. This is the second widely used pubsub command for online presence and with this and MONITOR we should have support for most if not all IRC servers.
Diffstat (limited to 'src/net/java/sip')
-rw-r--r--src/net/java/sip/communicator/impl/protocol/irc/ISupport.java6
-rw-r--r--src/net/java/sip/communicator/impl/protocol/irc/MonitorPresenceWatcher.java1
-rw-r--r--src/net/java/sip/communicator/impl/protocol/irc/PresenceManager.java127
-rw-r--r--src/net/java/sip/communicator/impl/protocol/irc/WatchPresenceWatcher.java339
4 files changed, 439 insertions, 34 deletions
diff --git a/src/net/java/sip/communicator/impl/protocol/irc/ISupport.java b/src/net/java/sip/communicator/impl/protocol/irc/ISupport.java
index 86ee6e4..66e9895 100644
--- a/src/net/java/sip/communicator/impl/protocol/irc/ISupport.java
+++ b/src/net/java/sip/communicator/impl/protocol/irc/ISupport.java
@@ -43,7 +43,11 @@ public enum ISupport
/**
* Maximum number of entries in the MONITOR list supported by this server.
*/
- MONITOR;
+ MONITOR,
+ /**
+ * Maximum number of entries in the WATCH list supported by this server.
+ */
+ WATCH;
/**
* Pattern for parsing ChanLimit ISUPPORT parameter.
diff --git a/src/net/java/sip/communicator/impl/protocol/irc/MonitorPresenceWatcher.java b/src/net/java/sip/communicator/impl/protocol/irc/MonitorPresenceWatcher.java
index b4f41e8..31e1405 100644
--- a/src/net/java/sip/communicator/impl/protocol/irc/MonitorPresenceWatcher.java
+++ b/src/net/java/sip/communicator/impl/protocol/irc/MonitorPresenceWatcher.java
@@ -141,6 +141,7 @@ class MonitorPresenceWatcher
LOGGER.trace("Removing nick '" + nick + "' from MONITOR watch list.");
this.nickWatchList.remove(nick);
this.irc.rawMessage("MONITOR - " + nick);
+ // FIXME Also remove from monitoredNicks list!
}
/**
diff --git a/src/net/java/sip/communicator/impl/protocol/irc/PresenceManager.java b/src/net/java/sip/communicator/impl/protocol/irc/PresenceManager.java
index 5873e46..4f658bd 100644
--- a/src/net/java/sip/communicator/impl/protocol/irc/PresenceManager.java
+++ b/src/net/java/sip/communicator/impl/protocol/irc/PresenceManager.java
@@ -84,6 +84,16 @@ public class PresenceManager
private final Integer isupportMonitor;
/**
+ * Maximum size of WATCH list allowed by server.
+ *
+ * <p>
+ * This value is not guaranteed, so it may be <tt>null</tt>. If it is
+ * <tt>null</tt> this means that WATCH is not supported by this server.
+ * </p>
+ */
+ private final Integer isupportWatch;
+
+ /**
* Server identity.
*/
private final AtomicReference<String> serverIdentity =
@@ -152,42 +162,59 @@ public class PresenceManager
// TODO move parse methods to ISupport enum type
this.isupportAwayLen = parseISupportAwayLen(this.connectionState);
this.isupportMonitor = parseISupportMonitor(this.connectionState);
- if (config.isContactPresenceTaskEnabled())
+ this.isupportWatch = parseISupportWatch(this.connectionState);
+ if (this.isupportMonitor != null)
{
- if (this.isupportMonitor == null)
- {
- this.watcher =
- new BasicPollerPresenceWatcher(this.irc,
- this.connectionState, this.operationSet, nickWatchList,
- this.serverIdentity);
- }
- else
- {
- // Share a list of monitored nicks between the
- // MonitorPresenceWatcher and the BasicPollerPresenceWatcher.
- // Now it is possible for the basic poller to determine whether
- // or not to poll for a certain nick, such that we do not poll
- // nicks that are already monitored.
- final SortedSet<String> monitoredNicks =
- Collections.synchronizedSortedSet(new TreeSet<String>());
- this.watcher =
- new MonitorPresenceWatcher(this.irc, this.connectionState,
- nickWatchList, monitoredNicks, this.operationSet,
- this.isupportMonitor);
- // Create a dynamic set that automatically computes the
- // difference between the full nick list and the list of nicks
- // that are subscribed to MONITOR. The difference will be the
- // result that is used by the basic poller.
- final Set<String> unmonitoredNicks =
- new DynamicDifferenceSet<String>(nickWatchList,
- monitoredNicks);
- new BasicPollerPresenceWatcher(this.irc, this.connectionState,
- this.operationSet, unmonitoredNicks, this.serverIdentity);
- }
+ // Share a list of monitored nicks between the
+ // MonitorPresenceWatcher and the BasicPollerPresenceWatcher.
+ // Now it is possible for the basic poller to determine whether
+ // or not to poll for a certain nick, such that we do not poll
+ // nicks that are already monitored.
+ final SortedSet<String> monitoredNicks =
+ Collections.synchronizedSortedSet(new TreeSet<String>());
+ this.watcher =
+ new MonitorPresenceWatcher(this.irc, this.connectionState,
+ nickWatchList, monitoredNicks, this.operationSet,
+ this.isupportMonitor);
+ // FIXME only set up basic poller if option enabled?
+ // Create a dynamic set that automatically computes the
+ // difference between the full nick list and the list of nicks
+ // that are subscribed to MONITOR. The difference will be the
+ // result that is used by the basic poller.
+ final Set<String> unmonitoredNicks =
+ new DynamicDifferenceSet<String>(nickWatchList, monitoredNicks);
+ new BasicPollerPresenceWatcher(this.irc, this.connectionState,
+ this.operationSet, unmonitoredNicks, this.serverIdentity);
}
- else
+ else if (this.isupportWatch != null)
+ {
+ // Share a list of monitored nicks between the
+ // WatchPresenceWatcher and the BasicPollerPresenceWatcher.
+ // Now it is possible for the basic poller to determine whether
+ // or not to poll for a certain nick, such that we do not poll
+ // nicks that are already monitored.
+ final SortedSet<String> monitoredNicks =
+ Collections.synchronizedSortedSet(new TreeSet<String>());
+ this.watcher =
+ new WatchPresenceWatcher(this.irc, this.connectionState,
+ nickWatchList, monitoredNicks, this.operationSet,
+ this.isupportWatch);
+ // FIXME only set up basic poller if option enabled?
+ // Create a dynamic set that automatically computes the
+ // difference between the full nick list and the list of nicks
+ // that are subscribed to WATCH. The difference will be the
+ // result that is used by the basic poller.
+ final Set<String> unmonitoredNicks =
+ new DynamicDifferenceSet<String>(nickWatchList, monitoredNicks);
+ new BasicPollerPresenceWatcher(this.irc, this.connectionState,
+ this.operationSet, unmonitoredNicks, this.serverIdentity);
+ }
+ else if (config.isContactPresenceTaskEnabled())
{
- // FIXME replace with NOOP watcher for code simplicity?
+ this.watcher =
+ new BasicPollerPresenceWatcher(this.irc, this.connectionState,
+ this.operationSet, nickWatchList, this.serverIdentity);
+ } else {
this.watcher = null;
}
}
@@ -260,6 +287,40 @@ public class PresenceManager
}
/**
+ * Parse the ISUPPORT parameter for WATCH command support and list size.
+ *
+ * @param state the connection state
+ * @return Returns instance with maximum number of entries in WATCH list.
+ * Additionally, having this WATCH property available, indicates
+ * that WATCH is supported by the server.
+ */
+ private Integer parseISupportWatch(final IIRCState state)
+ {
+ final String value =
+ state.getServerOptions().getKey(ISupport.WATCH.name());
+ if (value == null)
+ {
+ LOGGER.trace("No ISUPPORT parameter " + ISupport.WATCH.name()
+ + " available.");
+ return null;
+ }
+ if (LOGGER.isDebugEnabled())
+ {
+ LOGGER.debug("Setting ISUPPORT parameter " + ISupport.WATCH.name()
+ + " to " + value);
+ }
+ try
+ {
+ return new Integer(value);
+ }
+ catch (RuntimeException e)
+ {
+ LOGGER.warn("Failed to parse WATCH value.", e);
+ return null;
+ }
+ }
+
+ /**
* Check current Away state.
*
* @return returns <tt>true</tt> if away or <tt>false</tt> if not away
diff --git a/src/net/java/sip/communicator/impl/protocol/irc/WatchPresenceWatcher.java b/src/net/java/sip/communicator/impl/protocol/irc/WatchPresenceWatcher.java
new file mode 100644
index 0000000..53ed501
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/irc/WatchPresenceWatcher.java
@@ -0,0 +1,339 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.protocol.irc;
+
+import java.util.*;
+
+import net.java.sip.communicator.util.*;
+
+import com.ircclouds.irc.api.*;
+import com.ircclouds.irc.api.domain.messages.*;
+import com.ircclouds.irc.api.state.*;
+
+/**
+ * WATCH presence watcher.
+ *
+ * @author Danny van Heumen
+ */
+class WatchPresenceWatcher
+ implements PresenceWatcher
+{
+ /**
+ * Static overhead in message payload required for 'WATCH ' command.
+ */
+ private static final int WATCH_ADD_CMD_STATIC_OVERHEAD = 6;
+
+ /**
+ * Logger.
+ */
+ private static final Logger LOGGER = Logger
+ .getLogger(WatchPresenceWatcher.class);
+
+ /**
+ * IRCApi instance.
+ */
+ private final IRCApi irc;
+
+ /**
+ * IRC connection state.
+ */
+ private final IIRCState connectionState;
+
+ /**
+ * Complete nick watch list.
+ */
+ private final Set<String> nickWatchList;
+
+ /**
+ * Constructor.
+ *
+ * @param irc the IRCApi instance
+ * @param connectionState the connection state
+ * @param nickWatchList SYNCHRONIZED the nick watch list
+ * @param monitored SYNCHRONIZED The shared collection which contains all
+ * the nicks that are confirmed to be subscribed to the MONITOR
+ * command.
+ * @param operationSet the persistent presence operation set
+ */
+ WatchPresenceWatcher(final IRCApi irc, final IIRCState connectionState,
+ final Set<String> nickWatchList, final Set<String> monitored,
+ final OperationSetPersistentPresenceIrcImpl operationSet,
+ final int maxListSize)
+ {
+ if (irc == null)
+ {
+ throw new IllegalArgumentException("irc cannot be null");
+ }
+ this.irc = irc;
+ if (connectionState == null)
+ {
+ throw new IllegalArgumentException(
+ "connectionState cannot be null");
+ }
+ this.connectionState = connectionState;
+ if (nickWatchList == null)
+ {
+ throw new IllegalArgumentException("nickWatchList cannot be null");
+ }
+ this.nickWatchList = nickWatchList;
+ this.irc.addListener(new WatchReplyListener(monitored, operationSet));
+ setUpWatch(this.irc, this.nickWatchList, maxListSize);
+ LOGGER.debug("WATCH presence watcher initialized.");
+ }
+
+ /**
+ * Set up monitor based on the nick watch list in its current state.
+ *
+ * Created a static method as not to interfere too much with a state that is
+ * still being initialized.
+ */
+ private static void setUpWatch(final IRCApi irc,
+ final Collection<String> nickWatchList, final int maxListSize)
+ {
+ List<String> current;
+ synchronized (nickWatchList)
+ {
+ current = new LinkedList<String>(nickWatchList);
+ }
+ if (current.size() > maxListSize)
+ {
+ // cut off list to maximum number of entries allowed by server
+ current = current.subList(0, maxListSize);
+ }
+ final int maxLength = 510 - WATCH_ADD_CMD_STATIC_OVERHEAD;
+ final StringBuilder query = new StringBuilder();
+ for (String nick : current)
+ {
+ if (query.length() + nick.length() + 2 > maxLength)
+ {
+ // full payload, send monitor query now
+ irc.rawMessage("WATCH " + query);
+ query.delete(0, query.length());
+ }
+ else if (query.length() > 0)
+ {
+ query.append(" ");
+ }
+ query.append('+').append(nick);
+ }
+ if (query.length() > 0)
+ {
+ // send query for remaining nicks
+ irc.rawMessage("WATCH " + query);
+ }
+ }
+
+ @Override
+ public void add(final String nick)
+ {
+ LOGGER.trace("Adding nick '" + nick + "' to WATCH watch list.");
+ this.nickWatchList.add(nick);
+ this.irc.rawMessage("WATCH +" + nick);
+ }
+
+ @Override
+ public void remove(final String nick)
+ {
+ LOGGER.trace("Removing nick '" + nick + "' from WATCH watch list.");
+ this.nickWatchList.remove(nick);
+ this.irc.rawMessage("WATCH -" + nick);
+ }
+
+ /**
+ * Listener for WATCH replies.
+ *
+ * @author Danny van Heumen
+ */
+ private final class WatchReplyListener
+ extends AbstractIrcMessageListener
+ {
+ /**
+ * Numeric message id for notification that user logged on.
+ */
+ private static final int IRC_RPL_LOGON = 600;
+
+ /**
+ * Numeric message id for notification that user logged off.
+ */
+ private static final int IRC_RPL_LOGOFF = 601;
+
+ /**
+ * Numeric message id for when nick is removed from WATCH list.
+ */
+ private static final int IRC_RPL_WATCHOFF = 602;
+
+ /**
+ * Numeric message id for ONLINE nick response.
+ */
+ private static final int IRC_RPL_NOWON = 604;
+
+ /**
+ * Numeric message id for OFFLINE nick response.
+ */
+ private static final int IRC_RPL_NOWOFF = 605;
+
+ // /**
+ // * Numeric message id for MONLIST entry.
+ // */
+ // private static final int IRC_RPL_MONLIST = 732;
+ //
+ // /**
+ // * Numeric message id for ENDOFMONLIST.
+ // */
+ // private static final int IRC_RPL_ENDOFMONLIST = 733;
+
+ /**
+ * Error message signaling full list. Nick list provided are all nicks
+ * that failed to be added to the WATCH list.
+ */
+ private static final int IRC_ERR_LISTFULL = 512;
+
+ /**
+ * Operation set persistent presence instance.
+ */
+ private final OperationSetPersistentPresenceIrcImpl operationSet;
+
+ /**
+ * Set of nicks that are confirmed to be monitored by the server.
+ */
+ private final Set<String> monitoredNickList;
+
+ // TODO Update to act on onClientError once available.
+
+ /**
+ * Constructor.
+ *
+ * @param monitored SYNCHRONIZED Collection of monitored nicks. This
+ * collection will be updated with all nicks that are
+ * confirmed to be subscribed by the WATCH command.
+ * @param operationSet the persistent presence opset used to update nick
+ * presence statuses.
+ */
+ public WatchReplyListener(final Set<String> monitored,
+ final OperationSetPersistentPresenceIrcImpl operationSet)
+ {
+ super(WatchPresenceWatcher.this.irc,
+ WatchPresenceWatcher.this.connectionState);
+ if (operationSet == null)
+ {
+ throw new IllegalArgumentException(
+ "operationSet cannot be null");
+ }
+ this.operationSet = operationSet;
+ if (monitored == null)
+ {
+ throw new IllegalArgumentException("monitored cannot be null");
+ }
+ this.monitoredNickList = monitored;
+ }
+
+ /**
+ * Numeric messages received in response to WATCH commands or presence
+ * updates.
+ */
+ @Override
+ public void onServerNumericMessage(final ServerNumericMessage msg)
+ {
+ final String nick;
+ switch (msg.getNumericCode())
+ {
+ case IRC_RPL_NOWON:
+ nick = parseWatchResponse(msg.getText());
+ monitoredNickList.add(nick);
+ update(nick, IrcStatusEnum.ONLINE);
+ break;
+ case IRC_RPL_LOGON:
+ nick = parseWatchResponse(msg.getText());
+ update(nick, IrcStatusEnum.ONLINE);
+ break;
+ case IRC_RPL_NOWOFF:
+ nick = parseWatchResponse(msg.getText());
+ monitoredNickList.add(nick);
+ update(nick, IrcStatusEnum.OFFLINE);
+ break;
+ case IRC_RPL_LOGOFF:
+ nick = parseWatchResponse(msg.getText());
+ update(nick, IrcStatusEnum.OFFLINE);
+ break;
+ case IRC_RPL_WATCHOFF:
+ nick = parseWatchResponse(msg.getText());
+ monitoredNickList.remove(nick);
+ break;
+ case IRC_ERR_LISTFULL:
+ LOGGER.debug("WATCH list is full. Nick was not added. "
+ + "Fall back Basic Poller will be used if it is enabled. ("
+ + msg.getText() + ")");
+ break;
+ }
+ }
+
+ /**
+ * Update all monitored nicks upon receiving a server-side QUIT message
+ * for local user.
+ */
+ @Override
+ public void onUserQuit(QuitMessage msg)
+ {
+ super.onUserQuit(msg);
+ if (localUser(msg.getSource().getNick())) {
+ updateAll(IrcStatusEnum.OFFLINE);
+ }
+ }
+
+ /**
+ * Update all monitored nicks upon receiving a server-side ERROR
+ * response.
+ */
+ @Override
+ public void onError(ErrorMessage msg)
+ {
+ super.onError(msg);
+ updateAll(IrcStatusEnum.OFFLINE);
+ }
+
+ /**
+ * Parse response messages.
+ *
+ * @param message the message
+ * @return Returns the list of targets extracted.
+ */
+ private String parseWatchResponse(final String message)
+ {
+ final String[] parts = message.split(" ");
+ return parts[0];
+ }
+
+ /**
+ * Update all monitored nicks to specified status.
+ *
+ * @param status the desired status
+ */
+ private void updateAll(final IrcStatusEnum status)
+ {
+ final LinkedList<String> nicks;
+ synchronized (monitoredNickList)
+ {
+ nicks = new LinkedList<String>(monitoredNickList);
+ }
+ for (String nick : nicks)
+ {
+ update(nick, status);
+ }
+ }
+
+ /**
+ * Update specified nick to specified presence status.
+ *
+ * @param nick the target nick
+ * @param status the current status
+ */
+ private void update(final String nick, final IrcStatusEnum status)
+ {
+ this.operationSet.updateNickContactPresence(nick, status);
+ }
+ }
+}