package jrummikub.control.network; import java.util.UUID; import javax.swing.SwingUtilities; import jrummikub.model.GameSettings; import jrummikub.util.Event; import jrummikub.util.Event1; import jrummikub.util.IEvent; import jrummikub.util.IEvent1; import jrummikub.util.LoginData; import jrummikub.view.IGameListPanel.GameData; import org.jivesoftware.smack.Connection; import org.jivesoftware.smack.PacketListener; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.filter.AndFilter; import org.jivesoftware.smack.filter.PacketExtensionFilter; import org.jivesoftware.smack.filter.PacketTypeFilter; import org.jivesoftware.smack.packet.DefaultPacketExtension; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.packet.XMPPError; import org.jivesoftware.smack.packet.XMPPError.Type; import org.jivesoftware.smack.util.Base64; import org.jivesoftware.smackx.muc.DiscussionHistory; import org.jivesoftware.smackx.muc.MultiUserChat; class ConnectionControl { private final static String ELEMENT_NAME = "rummikub"; private final static String NAMESPACE = "http://home.universe-factory.net/rummikub/"; private final LoginData loginData; private volatile Connection connection; private volatile MultiUserChat muc; private Event connectedEvent = new Event(); private Event connectionFailedEvent = new Event(); private Event1 gameOfferEvent = new Event1(); private Event1 gameWithdrawalEvent = new Event1(); private volatile GameData offeredGame; ConnectionControl(LoginData loginData) { this.loginData = loginData; } String getNickname() { return muc.getNickname(); } void connect() { new Thread(new ConnectRunner()).start(); } void disconnect() { connectedEvent = new Event(); connectionFailedEvent = new Event(); new Thread(new DisconnectRunner()).start(); } IEvent getConnectedEvent() { return connectedEvent; } IEvent getConnectionFailedEvent() { return connectionFailedEvent; } IEvent1 getGameOfferEvent() { return gameOfferEvent; } IEvent1 getGameWithdrawalEvent() { return gameWithdrawalEvent; } void offerGame(GameData data) { offeredGame = data; sendGameOffer(); } void withdrawGame(UUID uuid) { offeredGame = null; new Thread(new SendGameWithdrawRunner(uuid)).start(); } private void sendGameOffer() { new Thread(new SendGameOfferRunner(offeredGame)).start(); } private static void emitLater(final Event event) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { event.emit(); } }); } private Message createMessage(PacketExtension extension) { Message message = muc.createMessage(); message.addExtension(extension); return message; } private static DefaultPacketExtension createJRummikubExtension() { return new DefaultPacketExtension(ELEMENT_NAME, NAMESPACE); } private void processPacket(Packet packet) { DefaultPacketExtension extension = (DefaultPacketExtension) packet .getExtension(ELEMENT_NAME, NAMESPACE); String messageType = extension.getValue("messageType"); if (messageType.equals("game_offer")) { String host = packet.getFrom(); host = host.substring(host.indexOf('/') + 1); UUID uuid = UUID.fromString(extension.getValue("uuid")); GameSettings settings = (GameSettings) Base64.decodeToObject(extension .getValue("gameSettings")); GameData gameData = new GameData(uuid, settings, host); gameOfferEvent.emit(gameData); } else if (messageType.equals("game_withdrawal")) { gameWithdrawalEvent.emit(UUID.fromString(extension.getValue("uuid"))); } else if (messageType.equals("game_request")) { if (offeredGame != null) { sendGameOffer(); } } } private Message createGameOfferMessage(GameData data) { DefaultPacketExtension extension = createJRummikubExtension(); extension.setValue("messageType", "game_offer"); extension.setValue("uuid", data.getGameID().toString()); extension.setValue("gameSettings", Base64.encodeObject(data.getGameSettings(), Base64.GZIP)); return createMessage(extension); } private Message createGameWithdrawMessage(UUID uuid) { DefaultPacketExtension extension = createJRummikubExtension(); extension.setValue("messageType", "game_withdrawal"); extension.setValue("uuid", uuid.toString()); return createMessage(extension); } private Message createGameRequestMessage() { DefaultPacketExtension extension = createJRummikubExtension(); extension.setValue("messageType", "game_request"); return createMessage(extension); } private class ConnectRunner implements Runnable { @Override public void run() { synchronized (ConnectionControl.this) { connection = new XMPPConnection(loginData.getServerName()); connection.addPacketListener(new PacketListener() { @Override public void processPacket(final Packet packet) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { ConnectionControl.this.processPacket(packet); } }); } }, new AndFilter(new PacketTypeFilter(Message.class), new PacketExtensionFilter(ELEMENT_NAME, NAMESPACE))); try { doConnect(); } catch (XMPPException e) { connection.disconnect(); connection = null; // TODO Auto-generated catch block e.printStackTrace(); emitLater(connectionFailedEvent); } } } private void doConnect() throws XMPPException { connection.connect(); connection.login(loginData.getUserName(), loginData.getPassword(), "JRummikub"); muc = new MultiUserChat(connection, loginData.getChannelName()); DiscussionHistory history = new DiscussionHistory(); history.setMaxStanzas(0); String nickname = loginData.getUserName(); // Loop until a unused nickname is found while (true) { try { muc.join(nickname, null, history, 10000); break; // Join was successful, break the loop } catch (XMPPException e) { XMPPError error = e.getXMPPError(); if (error.getType() == Type.CANCEL && error.getCode() == 409) { // There was a conflict, try again with another // nickname nickname += "_"; continue; } else { // An unknown error has occurred, cancel connect throw e; } } } emitLater(connectedEvent); new Thread(new SendGameRequestRunner()).start(); } } private class SendGameOfferRunner implements Runnable { private GameData data; public SendGameOfferRunner(GameData data) { this.data = data; } @Override public void run() { synchronized (ConnectionControl.this) { if (connection != null) { connection.sendPacket(createGameOfferMessage(data)); } } } } private class SendGameWithdrawRunner implements Runnable { private UUID uuid; public SendGameWithdrawRunner(UUID uuid) { this.uuid = uuid; } @Override public void run() { synchronized (ConnectionControl.this) { if (connection != null) { connection.sendPacket(createGameWithdrawMessage(uuid)); } } } } private class SendGameRequestRunner implements Runnable { @Override public void run() { synchronized (ConnectionControl.this) { if (connection != null) { connection.sendPacket(createGameRequestMessage()); } } } } private class DisconnectRunner implements Runnable { @Override public void run() { synchronized (ConnectionControl.this) { if (connection != null) { connection.disconnect(); connection = null; } } } } }