/*FreeMind - A Program for creating and viewing Mindmaps *Copyright (C) 2000-2008 Joerg Mueller, Daniel Polansky, Christian Foltin and others. * *See COPYING for Details * *This program is free software; you can redistribute it and/or *modify it under the terms of the GNU General Public License *as published by the Free Software Foundation; either version 2 *of the License, or (at your option) any later version. * *This program 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 General Public License for more details. * *You should have received a copy of the GNU General Public License *along with this program; if not, write to the Free Software *Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * Created on 28.12.2008 */ package plugins.collaboration.socket; import java.io.IOException; import java.io.Writer; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketTimeoutException; import java.util.Vector; import javax.swing.SwingUtilities; import freemind.common.NumberProperty; import freemind.common.StringProperty; import freemind.controller.actions.generated.instance.CollaborationGoodbye; import freemind.controller.actions.generated.instance.CollaborationUserInformation; import freemind.extensions.DontSaveMarker; import freemind.extensions.PermanentNodeHook; import freemind.main.Resources; import freemind.main.Tools; import freemind.main.XMLElement; import freemind.modes.MindMapNode; import freemind.modes.mindmapmode.MindMapController; import freemind.view.mindmapview.NodeView; /** * @author foltin * */ public class MindMapMaster extends SocketBasics implements PermanentNodeHook, DontSaveMarker { /** * */ public static final int SOCKET_TIMEOUT_IN_MILLIES = 500; MasterThread mListener = null; ServerSocket mServer; Vector mConnections = new Vector(); protected boolean mLockEnabled = false; private String mLockMutex = ""; private int mPort; private String mLockId; private long mLockedAt; private String mLockUserName; private boolean mMasterStarted; private class MasterThread extends TerminateableThread { private static final long TIME_BETWEEN_USER_INFORMATION_IN_MILLIES = 5000; private static final long TIME_FOR_ORPHANED_LOCK = 5000; private long mLastTimeUserInformationSent = 0; /** * @param pName */ public MasterThread() { super("Master"); } /* * (non-Javadoc) * * @see plugins.collaboration.socket.TerminateableThread#processAction() */ public boolean processAction() throws Exception { try { logger.finest("Waiting for message"); Socket client = mServer.accept(); logger.info("Received new client."); client.setSoTimeout(SOCKET_TIMEOUT_IN_MILLIES); ServerCommunication c = new ServerCommunication( MindMapMaster.this, client, getMindMapController()); c.start(); synchronized (mConnections) { mConnections.addElement(c); } } catch (SocketTimeoutException e) { } final long now = System.currentTimeMillis(); if (now - mLastTimeUserInformationSent > TIME_BETWEEN_USER_INFORMATION_IN_MILLIES) { mLastTimeUserInformationSent = now; CollaborationUserInformation userInfo = getMasterInformation(); synchronized (mConnections) { for (int i = 0; i < mConnections.size(); i++) { try { final ServerCommunication connection = (ServerCommunication) mConnections .elementAt(i); /* to each server, the IP address is chosen that belongs to this connection. * E.g. if the connection is routed over one of several network interfaces, * the address of this interface is reported. */ userInfo.setMasterIp(connection.getIpToSocket()); connection.send(userInfo); } catch (Exception e) { freemind.main.Resources.getInstance().logException( e); } } } } // timeout such that lock can't be held forever synchronized (mLockMutex) { if (mLockEnabled && now - mLockedAt > TIME_FOR_ORPHANED_LOCK) { logger.warning("Release lock " + mLockId + " held by " + mLockUserName); clearLock(); } } return true; } } public synchronized void removeConnection(ServerCommunication client) { synchronized (mConnections) { mConnections.remove(client); } // correct the map title, as we probably don't have clients anymore setTitle(); } protected void setTitle() { getMindMapController().getController().setTitle(); } public void startupMapHook() { super.startupMapHook(); // Restart check, as the startup command is given, even if the mindmap // is changed via // the tab bar. So, this method must be idempotent... if (mMasterStarted) { // we were already here, so return; } MindMapController controller = getMindMapController(); final StringProperty passwordProperty = new StringProperty( PASSWORD_DESCRIPTION, PASSWORD); final StringProperty passwordProperty2 = new StringProperty( PASSWORD_VERIFICATION_DESCRIPTION, PASSWORD_VERIFICATION); // StringProperty bindProperty = new StringProperty( // "IP address of the local machine, or 0.0.0.0 if ", "Host"); final NumberProperty portProperty = getPortProperty(); Vector controls = new Vector(); controls.add(passwordProperty); controls.add(passwordProperty2); // controls.add(bindProperty); controls.add(portProperty); FormDialog dialog = new FormDialog(controller); dialog.setUp(controls, new FormDialogValidator() { public boolean isValid() { logger.finest("Output valid?"); return Tools.safeEquals(passwordProperty.getValue(), passwordProperty2.getValue()); } }); if (!dialog.isSuccess()) { switchMeOff(); return; } /* Store port value in preferences. */ setPortProperty(portProperty); mPassword = passwordProperty.getValue(); // start server: logger.info("Start server..."); mMasterStarted = true; try { mPort = getPortProperty().getIntValue(); mServer = new ServerSocket(mPort); mServer.setSoTimeout(SOCKET_TIMEOUT_IN_MILLIES); mListener = new MasterThread(); mListener.start(); } catch (Exception e) { freemind.main.Resources.getInstance().logException(e); controller.getController().errorMessage( Resources.getInstance().format( SOCKET_CREATION_EXCEPTION_MESSAGE, new Object[] { portProperty.getValue(), e.getMessage() })); switchMeOff(); return; } registerFilter(); logger.info("Starting server. Done."); setTitle(); } public void switchMeOff() { final MindMapController mindMapController = getMindMapController(); // this is not nice, but in the starting phase of the hook, it can't be switched off. SwingUtilities.invokeLater(new Runnable() { public void run() { togglePermanentHook(mindMapController, MASTER_HOOK_LABEL); }}); } public void loadFrom(XMLElement pChild) { // this plugin should not be saved. } public void save(XMLElement pXml) { // this plugin should not be saved. // nothing to do. } public void shutdownMapHook() { deregisterFilter(); if (mListener != null) { signalEndOfSession(); mListener.commitSuicide(); } try { if (mServer != null) { mServer.close(); } } catch (IOException e) { freemind.main.Resources.getInstance().logException(e); } mMasterStarted = false; super.shutdownMapHook(); } /** * */ private void signalEndOfSession() { CollaborationGoodbye goodbye = new CollaborationGoodbye(); goodbye.setUserId(Tools.getUserName()); synchronized (mConnections) { for (int i = 0; i < mConnections.size(); i++) { final ServerCommunication serverCommunication = (ServerCommunication) mConnections .elementAt(i); try { serverCommunication.send(goodbye); serverCommunication.commitSuicide(); serverCommunication.close(); } catch (IOException e) { freemind.main.Resources.getInstance().logException(e); } } mConnections.clear(); } } public void onAddChild(MindMapNode pAddedChildNode) { } public void onAddChildren(MindMapNode pAddedChild) { } public void onLostFocusNode(NodeView pNodeView) { } public void onNewChild(MindMapNode pNewChildNode) { } public void onRemoveChild(MindMapNode pOldChildNode) { } public void onRemoveChildren(MindMapNode pOldChildNode, MindMapNode pOldDad) { } public void onFocusNode(NodeView pNodeView) { } public void onUpdateChildrenHook(MindMapNode pUpdatedNode) { } public void onUpdateNodeHook() { } public void onViewCreatedHook(NodeView pNodeView) { } public void onViewRemovedHook(NodeView pNodeView) { } public Integer getRole() { return ROLE_MASTER; } /* * (non-Javadoc) * * @see plugins.collaboration.socket.SocketBasics#getPort() */ public int getPort() { return mPort; } /* * (non-Javadoc) * * @see plugins.collaboration.socket.SocketBasics#lock() */ protected String lock(String pUserName) throws UnableToGetLockException, InterruptedException { synchronized (mLockMutex) { if (mLockEnabled) { throw new UnableToGetLockException(); } mLockEnabled = true; String lockId = "Lock_" + Math.random(); mLockId = lockId; mLockedAt = System.currentTimeMillis(); mLockUserName = pUserName; logger.info("New lock " + lockId + " by " + mLockUserName); return lockId; } } /* * (non-Javadoc) * * @see * plugins.collaboration.socket.SocketBasics#sendCommand(java.lang.String, * java.lang.String, java.lang.String) */ protected void broadcastCommand(String pDoAction, String pUndoAction, String pLockId) throws Exception { synchronized (mConnections) { for (int i = 0; i < mConnections.size(); i++) { ((ServerCommunication) mConnections.elementAt(i)).sendCommand( pDoAction, pUndoAction, pLockId); } } } /* * (non-Javadoc) * * @see plugins.collaboration.socket.SocketBasics#unlock() */ protected void unlock() { synchronized (mLockMutex) { if (!mLockEnabled) { throw new IllegalStateException(); } logger.fine("Release lock " + mLockId + " held by " + mLockUserName); clearLock(); } } public void clearLock() { mLockEnabled = false; mLockId = "none"; mLockUserName = null; } /* * (non-Javadoc) * * @see plugins.collaboration.socket.SocketBasics#shutdown() */ public void shutdown() { // TODO Auto-generated method stub } public String getLockId() { synchronized (mLockMutex) { if (!mLockEnabled) { throw new IllegalStateException(); } return mLockId; } } /* * (non-Javadoc) * * @see plugins.collaboration.socket.SocketBasics#getUsers() */ public String getUsers() { StringBuffer users = new StringBuffer(Tools.getUserName()); synchronized (mConnections) { for (int i = 0; i < mConnections.size(); i++) { users.append(','); users.append(' '); users.append(((ServerCommunication) mConnections.elementAt(i)) .getName()); } } return users.toString(); } public CollaborationUserInformation getMasterInformation() { CollaborationUserInformation userInfo = new CollaborationUserInformation(); userInfo.setUserIds(getUsers()); userInfo.setMasterHostname(Tools.getHostName()); userInfo.setMasterPort(getPort()); userInfo.setMasterIp(Tools.getHostIpAsString()); return userInfo; } public void processUnfinishedLinks() { } /* (non-Javadoc) * @see freemind.extensions.PermanentNodeHook#saveHtml(java.io.Writer) */ public void saveHtml(Writer pFileout) { } }