package org.limewire.xmpp.client.impl.messages.nosave;
import java.util.HashMap;
import java.util.Map;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.Packet;
import org.limewire.friend.api.Friend;
import org.limewire.friend.api.FriendException;
import org.limewire.friend.api.FriendPresence;
import org.limewire.friend.api.FriendPresenceEvent;
import org.limewire.friend.impl.feature.NoSave;
import org.limewire.friend.impl.feature.NoSaveFeature;
import org.limewire.friend.impl.feature.NoSaveStatus;
import org.limewire.listener.EventListener;
import org.limewire.listener.ListenerSupport;
import org.limewire.xmpp.client.impl.XMPPFriendConnectionImpl;
/**
* This class is responsible for:
*<pre>
*
* 1. Listening for google:nosave result packets.
* This class is a PacketListener, and is added to a jabber xmpp connection.
*
* 2. Keeping all presences in the currently logged in connection up to date
* with their nosave statuses by adding the nosave feature to the presence.
* This class tracks the nosave status for every friend.
*
* Sample google:nosave packet:
*
* <iq id="58jE1-6" to="limebuddytest1@gmail.com/vN/X2IGD1QFDF73EA8" type="result">
* <query xmlns="google:nosave">
* <item xmlns="google:nosave" jid="limebuddytest2@gmail.com" value="disabled"/>
* <item xmlns="google:nosave" jid="limebuddytest3@gmail.com" value="enabled"/>
* ...
*
* </pre>
*/
public class NoSaveIQListener implements PacketListener {
private final XMPPFriendConnectionImpl connection;
private Map<String, NoSave> noSaveMap = new HashMap<String, NoSave>();
private ListenerSupport<FriendPresenceEvent> friendPresenceSupport;
private EventListener<FriendPresenceEvent> friendPresenceListener;
private NoSaveIQListener(XMPPFriendConnectionImpl connection) {
this.connection = connection;
}
/**
* Factory method to create NoSaveIQListener. Purpose is separate creation via
* constructor from adding a FriendPresenceEvent listener
*
* @param connection XMPPConnectionImpl
* @param friendPresenceSupport FriendPresenceEvent listener manager
* @return NoSaveIQListener object
*/
public static NoSaveIQListener createNoSaveIQListener(XMPPFriendConnectionImpl connection,
ListenerSupport<FriendPresenceEvent> friendPresenceSupport) {
NoSaveIQListener noSaveIQListener = new NoSaveIQListener(connection);
noSaveIQListener.register(friendPresenceSupport);
return noSaveIQListener;
}
/**
* 1. Update this object's nosave state (nosave status for all friends)
* 2. For each friend's presence, add nosave feature to presence if necessary
* {@link #shouldAddFeature}
*
* @param packet NoSaveIQ packet
*/
@Override
public void processPacket(Packet packet) {
NoSaveIQ noSave = (NoSaveIQ)packet;
// go thru the nosave IQ packet, and go thru each item and update the nosaveStatus
Map<String, NoSave> friends = noSave.getNoSaveUsers();
updateNoSaveStatusMap(friends);
for (String friendName : friends.keySet()) {
Friend noSaveFriend = connection.getFriend(friendName);
// If friend is not yet known to the connection, skip it.
// This can occur upon login if the nosave IQ packet is processed before the roster packet is processed.
// It is ok to skip this friend name, because when the friend presence arrives it will automatically
// trigger the adding of the nosave feature if applicable.
if (noSaveFriend != null) {
// add feature (if necessary) to all of this friend's presences
for (FriendPresence presence : noSaveFriend.getPresences().values()) {
addNoSaveFeatureIfNecessary(presence, friends.get(friendName));
}
}
}
}
/**
* In adding jabber connection listener,
* used to specify to jabber connection that
* only nosave packets are processed.
*
* @return PacketFilter
*/
public PacketFilter getPacketFilter() {
return new PacketFilter(){
@Override
public boolean accept(Packet packet) {
return packet instanceof NoSaveIQ;
}
};
}
/**
* Called when this class is no longer relevant (such as when
* the end user signs off)
*/
public void cleanup() {
this.friendPresenceSupport.removeListener(friendPresenceListener);
}
private void register(ListenerSupport<FriendPresenceEvent> friendPresenceSupport) {
this.friendPresenceSupport = friendPresenceSupport;
this.friendPresenceListener = new FriendPresenceListener();
this.friendPresenceSupport.addListener(friendPresenceListener);
}
private void updateNoSaveStatusMap(Map<String, NoSave> noSaveMap) {
synchronized (this) {
this.noSaveMap = new HashMap<String, NoSave>(noSaveMap);
}
}
private void updateNewPresenceNoSave(FriendPresence presence) {
String friendId = presence.getFriend().getId();
NoSave noSaveStatus;
synchronized (this) {
noSaveStatus = noSaveMap.get(friendId);
}
if (noSaveStatus != null) {
addNoSaveFeatureIfNecessary(presence, noSaveStatus);
}
}
private void addNoSaveFeatureIfNecessary(FriendPresence presence, NoSave nosave) {
if (shouldAddFeature(presence, nosave)) {
presence.addFeature(new NoSaveFeature(new NoSaveStatusImpl(nosave, presence.getFriend().getId())));
}
}
/**
* Add feature to presence only if the presence currently doesn't have the feature
* or if the presence's current nosave feature status value is different than the correct one
* (the value most recently parsed from the nosave packet).
*/
private boolean shouldAddFeature(FriendPresence presence, NoSave nosave) {
if (presence.hasFeatures(NoSaveFeature.ID) &&
nosave == ((NoSaveFeature)presence.getFeature(NoSaveFeature.ID)).getFeature().getStatus()) {
return false;
} else {
return true;
}
}
/**
* This FriendPresenceEvent listener class is responsible for keeping new
* presences up to date with current nosave data.
*
*/
private class FriendPresenceListener implements EventListener<FriendPresenceEvent> {
@Override
public void handleEvent(FriendPresenceEvent event) {
// update all new presences with nosave data
if (event.getType() == FriendPresenceEvent.Type.ADDED) {
updateNewPresenceNoSave(event.getData());
}
}
}
/**
* Inner class impl of {@link NoSaveStatus} which uses its
* containing XMPPConnectionImpl member to send a nosave set
* packet in the toggleStatus method.
*/
private class NoSaveStatusImpl implements NoSaveStatus {
private final NoSave noSave;
private final String userName;
NoSaveStatusImpl(NoSave noSave, String userName) {
this.noSave = noSave;
this.userName = userName;
}
@Override
public NoSave getStatus() {
return noSave;
}
@Override
public void toggleStatus() throws FriendException {
NoSaveIQ noSaveMsg = NoSaveIQ.getNoSaveSetMessage(
userName, noSave == NoSave.ENABLED ? NoSave.DISABLED : NoSave.ENABLED);
NoSaveIQListener.this.connection.sendPacket(noSaveMsg);
}
}
}