/* * $Id$ * * Copyright (c) 2000-2012 by Rodney Kinney, Brent Easton * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License (LGPL) as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, copies are available * at http://www.opensource.org. */ package VASSAL.chat.jabber; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Properties; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTextField; import net.miginfocom.swing.MigLayout; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.Form; import org.jivesoftware.smackx.muc.MultiUserChat; import org.jivesoftware.smackx.muc.RoomInfo; import VASSAL.Info; import VASSAL.build.GameModule; import VASSAL.chat.LockableRoom; import VASSAL.chat.Player; import VASSAL.chat.SimpleRoom; import VASSAL.chat.SimpleStatus; import VASSAL.i18n.Resources; import VASSAL.tools.PropertiesEncoder; import VASSAL.tools.swing.Dialogs; public class JabberRoom extends SimpleRoom implements LockableRoom { public static final String CONFIG_NAME = "name"; //$NON-NLS-1$ public static final String CONFIG_LOCKED = "locked"; //$NON-NLS-1$ public static final String CONFIG_VASSAL_VERSION = "vasVer"; //$NON-NLS-1$ public static final String CONFIG_MIN_VASSAL_VERSION = "minVasVer"; //$NON-NLS-1$ public static final String CONFIG_MODULE_VERSION = "modVer"; //$NON-NLS-1$ public static final String CONFIG_MIN_MODULE_VERSION = "minModVer"; //$NON-NLS-1$ public static final String CONFIG_CRC_CHECK = "crcCheck"; //$NON-NLS-1$ public static final String CONFIG_CRC = "crc"; //$NON-NLS-1$ private static final String JABBER_MEMBERSONLY = "muc#roomconfig_membersonly"; //$NON-NLS-1$ private static final String JABBER_ALLOW_INVITES = "muc#roomconfig_allowinvites"; //$NON-NLS-1$ private static final String JABBER_CHANGE_SUBJECT = "muc#roomconfig_changesubject"; //$NON-NLS-1$ private static final String JABBER_MODERATED = "muc#roomconfig_moderatedroom"; //$NON-NLS-1$ private static final String JABBER_PASSWORD_PROTECTED = "muc#roomconfig_passwordprotectedroom"; //$NON-NLS-1$ private static final String JABBER_PERSISTENT = "muc#roomconfig_persistentroom"; //$NON-NLS-1$ private static final String JABBER_PUBLIC_ROOM = "muc#roomconfig_publicroom"; //$NON-NLS-1$ private String jid; private RoomInfo info; private boolean ownedByMe; private JabberClient client; private ArrayList<String> owners = new ArrayList<String>(); private Properties config = new Properties(); private JabberRoom(String name, String jid, RoomInfo info, JabberClient client) { super(name); this.jid = jid; this.info = info; this.client = client; config.clear(); config.put(CONFIG_NAME, name); } public String getJID() { return jid; } public boolean isLocked() { return info != null && info.isMembersOnly(); } public void setInfo(RoomInfo info) { this.info = info; } public void toggleLock(MultiUserChat muc) { try { if (!isLocked()) { lock(muc); } else { unlock(muc); } info = MultiUserChat.getRoomInfo(client.getConnection(), jid); } catch (XMPPException e) { e.printStackTrace(); return; } } protected void lock(MultiUserChat muc) throws XMPPException { final Form form = muc.getConfigurationForm().createAnswerForm(); form.setAnswer(JABBER_MEMBERSONLY, true); muc.sendConfigurationForm(form); } protected void unlock(MultiUserChat muc) throws XMPPException { final Form form = muc.getConfigurationForm().createAnswerForm(); form.setAnswer(JABBER_MEMBERSONLY, false); muc.sendConfigurationForm(form); } public MultiUserChat join(JabberClient client, JabberPlayer me) throws XMPPException { MultiUserChat chat = new MultiUserChat(client.getConnection(), getJID()); chat.join(StringUtils.parseName(me.getJid())); if (!chat.isJoined()) { return null; } try { // This is necessary to create the room if it doesn't already exist // Configure the options we needs explicitly, don't depend on the server supplied defaults final Form configForm = chat.getConfigurationForm().createAnswerForm(); configForm.setAnswer(JABBER_MEMBERSONLY, isStartLocked()); configForm.setAnswer(JABBER_ALLOW_INVITES, false); configForm.setAnswer(JABBER_CHANGE_SUBJECT, false); configForm.setAnswer(JABBER_MODERATED, false); configForm.setAnswer(JABBER_PASSWORD_PROTECTED, false); configForm.setAnswer(JABBER_PERSISTENT, false); configForm.setAnswer(JABBER_PUBLIC_ROOM, true); chat.sendConfigurationForm(configForm); ownedByMe = true; owners.clear(); addOwner(jid); } catch (XMPPException e) { // 403 code means the room already exists and user is not an owner if (e.getXMPPError() != null && e.getXMPPError().getCode() != 403) { throw e; } } chat.addMessageListener(client); return chat; } public boolean equals(Object o) { if (o instanceof JabberRoom) { JabberRoom r = (JabberRoom) o; return r.jid.equals(jid); } else { return false; } } public int hashCode() { return jid.hashCode(); } public boolean isOwnedByMe() { return ownedByMe; } public boolean isOwner(String jid) { return owners.contains(jid); } public void addOwner(String jid) { if (! owners.contains(jid)) { owners.add(jid); } } public void removeOwner(String jid) { owners.remove(jid); } public Player getOwningPlayer() { if (owners.size() == 0) { return null; } return getPlayer(owners.get(0)); } public void setConfig(Properties props) { config = props; setName(config.getProperty(CONFIG_NAME)); } public String encodeConfig() { final String s = new PropertiesEncoder(config).getStringValue(); return s == null ? "" : s; //$NON-NLS-1$ } public void decodeConfig(String s) { try { config = new PropertiesEncoder(s).getProperties(); setName(config.getProperty(CONFIG_NAME)); } catch (IOException e) { e.printStackTrace(); } } public boolean isStartLocked() { return "true".equals(config.getProperty(CONFIG_LOCKED)); //$NON-NLS-1$ } public boolean isMatchCrc() { return "true".equals(config.getProperty(CONFIG_CRC_CHECK)); //$NON-NLS-1$ } public String getCheckCrc() { return config.getProperty(CONFIG_CRC); } public String getVassalOption() { return config.getProperty(CONFIG_VASSAL_VERSION, ANY_OPTION); } public String getVassalVersion() { return config.getProperty(CONFIG_MIN_VASSAL_VERSION, Info.getVersion()); } public String getModuleOption() { return config.getProperty(CONFIG_MODULE_VERSION, ANY_OPTION); } public String getModuleVersion() { return config.getProperty(CONFIG_MIN_MODULE_VERSION, GameModule.getGameModule().getGameVersion()); } public void showConfig() { final JabberRoomConfig c = new JabberRoomConfig(config, false); Dialogs.showDialog(null, Resources.getString("Chat.room_configuration"), c, JOptionPane.PLAIN_MESSAGE, null, JOptionPane.OK_CANCEL_OPTION, null, null, null, null); //$NON-NLS-1$ } /** * Is the specified player allowed to join this room? * @param p A JabberPlayer * @return null = false, non-null = error message */ public String canJoin(JabberPlayer p) { // Owner can always join if (isOwnedByMe()) { return null; } // Check Vassal Version String option = getVassalOption(); if (!ANY_OPTION.equals(option)) { final String thisVassal = Info.getVersion(); final String targetVassal = getVassalVersion(); if (MINIMUM_OPTION.equals(option)) { if (Info.compareVersions(thisVassal, targetVassal) < 1) { return Resources.getString("Chat.bad_min_vassal", thisVassal, targetVassal); //$NON-NLS-1$ } } else { if (! thisVassal.equals(targetVassal)) { return Resources.getString("Chat.bad_vassal", thisVassal, targetVassal); //$NON-NLS-1$ } } } // Check Module Version option = getModuleOption(); if (!ANY_OPTION.equals(option)) { final String thisModule = GameModule.getGameModule().getGameVersion(); final String targetModule = getModuleVersion(); if (MINIMUM_OPTION.equals(option)) { if (Info.compareVersions(thisModule, targetModule) < 1) { return Resources.getString("Chat.bad_min_module", thisModule, targetModule); //$NON-NLS-1$ } } else { if (! thisModule.equals(targetModule)) { return Resources.getString("Chat.bad_module", thisModule, targetModule); //$NON-NLS-1$ } } } // Check CRC if (isMatchCrc()) { final String playerCRC = ((SimpleStatus) p.getStatus()).getCrc(); final String moduleCRC = getCheckCrc(); if (!moduleCRC.equals(playerCRC)) { return Resources.getString("Chat.bad_crc"); //$NON-NLS-1$ } } return null; } public static class Manager { private Map<String, JabberRoom> jidToRoom = new HashMap<String, JabberRoom>(); public JabberRoom getRoomByJID(JabberClient client, String jid) { return getRoomByJID(client, jid, ""); } public synchronized JabberRoom getRoomByJID(JabberClient client, String jid, String defaultName) { if (jid == null) { return null; } JabberRoom newRoom = jidToRoom.get(jid); if (newRoom == null) { String roomName = defaultName == null ? "" : defaultName; //$NON-NLS-1$ RoomInfo info = null; try { info = MultiUserChat.getRoomInfo(client.getConnection(), jid); } // FIXME: review error message catch (XMPPException e) { e.printStackTrace(); } newRoom = new JabberRoom(roomName, jid, info, client); jidToRoom.put(jid, newRoom); } return newRoom; } public synchronized JabberRoom getRoomByName(JabberClient client, String name) { String jid = StringUtils.escapeNode(client.getModule() + "-" + name).toLowerCase() + "@" + client.getConferenceService(); //$NON-NLS-1$ //$NON-NLS-2$ JabberRoom room = jidToRoom.get(jid); if (room == null) { room = new JabberRoom(name, jid, null, client); jidToRoom.put(jid, room); } return room; } public void deleteRoom(String jid) { jidToRoom.remove(jid); } public synchronized void clear() { jidToRoom.clear(); } } public static Properties configureNewRoom() { final JabberRoomConfig config = new JabberRoomConfig(); final Integer result = ((Integer) Dialogs.showDialog(null, Resources.getString("Chat.create_new_room"), config, JOptionPane.PLAIN_MESSAGE, null, JOptionPane.OK_CANCEL_OPTION, null, null, null, null)).intValue(); //$NON-NLS-1$ if (result != null && result.intValue() == 0) { return config.getProperties(); } return null; } private static final String MINIMUM_OPTION = "min"; //$NON-NLS-1$ private static final String ANY_OPTION = "any"; //$NON-NLS-1$ private static final String THIS_OPTION = "this"; //$NON-NLS-1$ private static final String MINIMUM_VERSION = Resources.getString("Chat.mimimum_version"); //$NON-NLS-1$ private static final String ANY_VERSION = Resources.getString("Chat.any_version"); //$NON-NLS-1$ private static final String THIS_VERSION = Resources.getString("Chat.this_version"); //$NON-NLS-1$ public static class JabberRoomConfig extends JPanel { private static final long serialVersionUID = 1L; private JTextField roomNameConfig; private JCheckBox startLockedConfig; private JCheckBox matchCrcConfig; private JTextField crcConfig; private JComboBox vassalVersionConfig; private JTextField minimumVassalVersionConfig; private JComboBox moduleVersionConfig; private JTextField minimumModuleVersionConfig; private String vassalVersion; private String moduleVersion; private boolean updateEnabled = true; static String versionToOption(String version) { if (MINIMUM_VERSION.equals(version)) { return MINIMUM_OPTION; } else if (THIS_VERSION.equals(version)) { return THIS_OPTION; } return ANY_OPTION; } static String optionToVersion(String option) { if (MINIMUM_OPTION.equals(option)) { return MINIMUM_VERSION; } else if (THIS_OPTION.equals(option)) { return THIS_VERSION; } return ANY_VERSION; } public JabberRoomConfig() { super(); vassalVersion = Info.getVersion(); moduleVersion = GameModule.getGameModule().getGameVersion(); setLayout(new MigLayout("insets dialog", "[align right][fill,grow]", "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ add(new JLabel(Resources.getString("Chat.new_room_name"))); //$NON-NLS-1$ roomNameConfig = new JTextField(); add(roomNameConfig, "wrap"); //$NON-NLS-1$ add(new JLabel(Resources.getString("Chat.start_locked"))); //$NON-NLS-1$ startLockedConfig = new JCheckBox(); add(startLockedConfig, "wrap"); //$NON-NLS-1$ add(new JLabel(Resources.getString("Chat.vassal_versions_allowed"))); //$NON-NLS-1$ vassalVersionConfig = new JComboBox(new String[] {ANY_VERSION, THIS_VERSION, MINIMUM_VERSION}); //$NON-NLS-1$ vassalVersionConfig.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent arg0) { updateVisibility(); }}); add(vassalVersionConfig); minimumVassalVersionConfig = new JTextField(12); minimumVassalVersionConfig.setText(vassalVersion); add(minimumVassalVersionConfig, "wrap"); //$NON-NLS-1$ add(new JLabel(Resources.getString("Chat.module_versions_allowed"))); //$NON-NLS-1$ moduleVersionConfig = new JComboBox(new String[] {ANY_VERSION, THIS_VERSION, MINIMUM_VERSION}); moduleVersionConfig.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent arg0) { updateVisibility(); }}); add(moduleVersionConfig); minimumModuleVersionConfig = new JTextField(12); minimumModuleVersionConfig.setText(moduleVersion); add(minimumModuleVersionConfig, "wrap"); //$NON-NLS-1$ add(new JLabel(Resources.getString("Chat.crc_match"))); //$NON-NLS-1$ matchCrcConfig = new JCheckBox(); matchCrcConfig.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { updateVisibility(); }}); add(matchCrcConfig); crcConfig = new JTextField(12); crcConfig.setText(Long.toHexString(GameModule.getGameModule().getCrc())); crcConfig.setEditable(false); add(crcConfig, "wrap"); //$NON-NLS-1$ updateVisibility(); } public JabberRoomConfig(Properties props) { this(); roomNameConfig.setText(props.getProperty(CONFIG_NAME)); startLockedConfig.setSelected("true".equals(props.getProperty(CONFIG_LOCKED))); //$NON-NLS-1$ vassalVersionConfig.setSelectedItem(optionToVersion(props.getProperty(CONFIG_VASSAL_VERSION, ANY_VERSION))); minimumVassalVersionConfig.setText(props.getProperty(CONFIG_MIN_VASSAL_VERSION, "")); //$NON-NLS-1$ moduleVersionConfig.setSelectedItem(optionToVersion(props.getProperty(CONFIG_MODULE_VERSION, ANY_VERSION))); minimumModuleVersionConfig.setText(props.getProperty(CONFIG_MIN_MODULE_VERSION, "")); //$NON-NLS-1$ matchCrcConfig.setSelected("true".equals(props.getProperty(CONFIG_CRC_CHECK))); //$NON-NLS-1$ crcConfig.setText(props.getProperty(CONFIG_CRC)); } public JabberRoomConfig(Properties props, boolean enabled) { this(props); setEnabled(enabled); } public boolean isUpdateEnabled() { return updateEnabled; } public void setEnabled(boolean enabled) { updateEnabled = enabled; updateVisibility(); } private void updateVisibility() { minimumVassalVersionConfig.setVisible(! ANY_VERSION.equals(vassalVersionConfig.getSelectedItem())); minimumModuleVersionConfig.setVisible(! ANY_VERSION.equals(moduleVersionConfig.getSelectedItem())); crcConfig.setVisible(matchCrcConfig.isSelected()); roomNameConfig.setEditable(isUpdateEnabled()); startLockedConfig.setEnabled(isUpdateEnabled()); vassalVersionConfig.setEnabled(isUpdateEnabled()); minimumVassalVersionConfig.setEditable(isUpdateEnabled() && vassalVersionConfig.getSelectedItem().equals(MINIMUM_VERSION)); moduleVersionConfig.setEnabled(isUpdateEnabled()); minimumModuleVersionConfig.setEditable(isUpdateEnabled() && moduleVersionConfig.getSelectedItem().equals(MINIMUM_VERSION)); matchCrcConfig.setEnabled(isUpdateEnabled()); } public Properties getProperties() { Properties props = new Properties(); props.put(CONFIG_NAME, roomNameConfig.getText()); props.put(CONFIG_LOCKED, Boolean.toString(startLockedConfig.isSelected())); final String vv = versionToOption((String) vassalVersionConfig.getSelectedItem()); props.put(CONFIG_VASSAL_VERSION, vv); if (!vv.equals(ANY_OPTION)) { props.put(CONFIG_MIN_VASSAL_VERSION, minimumVassalVersionConfig.getText()); } final String mv = versionToOption((String) moduleVersionConfig.getSelectedItem()); props.put(CONFIG_MODULE_VERSION, mv); if(!mv.equals(ANY_OPTION)) { props.put(CONFIG_MIN_MODULE_VERSION, minimumModuleVersionConfig.getText()); } props.put(CONFIG_CRC_CHECK, Boolean.toString(matchCrcConfig.isSelected())); if (matchCrcConfig.isSelected()) { props.put(CONFIG_CRC, crcConfig.getText()); } return props; } } }