/* * Copyright 2004 - 2009 Christian Sprajc. All rights reserved. * * This file is part of PowerFolder. * * PowerFolder 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. * * PowerFolder 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 PowerFolder. If not, see <http://www.gnu.org/licenses/>. * * $Id$ */ package de.dal33t.powerfolder; import java.io.Externalizable; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; import de.dal33t.powerfolder.clientserver.ServerClient; import de.dal33t.powerfolder.disk.Folder; import de.dal33t.powerfolder.disk.FolderRepository; import de.dal33t.powerfolder.light.AccountInfo; import de.dal33t.powerfolder.light.FileInfo; import de.dal33t.powerfolder.light.FolderInfo; import de.dal33t.powerfolder.light.MemberInfo; import de.dal33t.powerfolder.message.AbortDownload; import de.dal33t.powerfolder.message.AbortUpload; import de.dal33t.powerfolder.message.AddFriendNotification; import de.dal33t.powerfolder.message.ConfigurationLoadRequest; import de.dal33t.powerfolder.message.DownloadQueued; import de.dal33t.powerfolder.message.FileChunk; import de.dal33t.powerfolder.message.FileHistoryReply; import de.dal33t.powerfolder.message.FileHistoryRequest; import de.dal33t.powerfolder.message.FileList; import de.dal33t.powerfolder.message.FileListRequest; import de.dal33t.powerfolder.message.FileRequestCommand; import de.dal33t.powerfolder.message.FolderDBMaintCommando; import de.dal33t.powerfolder.message.FolderFilesChanged; import de.dal33t.powerfolder.message.FolderList; import de.dal33t.powerfolder.message.FolderListExt; import de.dal33t.powerfolder.message.FolderRelatedMessage; import de.dal33t.powerfolder.message.HandshakeCompleted; import de.dal33t.powerfolder.message.Identity; import de.dal33t.powerfolder.message.IdentityReply; import de.dal33t.powerfolder.message.Invitation; import de.dal33t.powerfolder.message.KnownNodes; import de.dal33t.powerfolder.message.Message; import de.dal33t.powerfolder.message.MessageListener; import de.dal33t.powerfolder.message.NodeInformation; import de.dal33t.powerfolder.message.Notification; import de.dal33t.powerfolder.message.Ping; import de.dal33t.powerfolder.message.Pong; import de.dal33t.powerfolder.message.Problem; import de.dal33t.powerfolder.message.RelayedMessage; import de.dal33t.powerfolder.message.ReplyFilePartsRecord; import de.dal33t.powerfolder.message.RequestDownload; import de.dal33t.powerfolder.message.RequestFilePartsRecord; import de.dal33t.powerfolder.message.RequestNodeInformation; import de.dal33t.powerfolder.message.RequestNodeList; import de.dal33t.powerfolder.message.RequestPart; import de.dal33t.powerfolder.message.ScanCommand; import de.dal33t.powerfolder.message.SearchNodeRequest; import de.dal33t.powerfolder.message.SettingsChange; import de.dal33t.powerfolder.message.StartUpload; import de.dal33t.powerfolder.message.StopUpload; import de.dal33t.powerfolder.message.TransferStatus; import de.dal33t.powerfolder.message.UDTMessage; import de.dal33t.powerfolder.message.clientserver.AccountStateChanged; import de.dal33t.powerfolder.net.ConnectionException; import de.dal33t.powerfolder.net.ConnectionHandler; import de.dal33t.powerfolder.net.InvalidIdentityException; import de.dal33t.powerfolder.net.PlainSocketConnectionHandler; import de.dal33t.powerfolder.transfer.Download; import de.dal33t.powerfolder.transfer.TransferManager; import de.dal33t.powerfolder.transfer.Upload; import de.dal33t.powerfolder.util.ConfigurationLoader; import de.dal33t.powerfolder.util.Debug; import de.dal33t.powerfolder.util.Filter; import de.dal33t.powerfolder.util.MessageListenerSupport; import de.dal33t.powerfolder.util.Profiling; import de.dal33t.powerfolder.util.ProfilingEntry; import de.dal33t.powerfolder.util.Reject; import de.dal33t.powerfolder.util.StringUtils; import de.dal33t.powerfolder.util.Util; import de.dal33t.powerfolder.util.Waiter; import de.dal33t.powerfolder.util.logging.LoggingManager; import de.dal33t.powerfolder.util.net.NetworkUtil; /** * A full quailfied member, can have a connection to interact with remote * member/friend/peer. * * @author <a href="mailto:totmacher@powerfolder.com">Christian Sprajc </a> * @version $Revision: 1.115 $ */ public class Member extends PFComponent implements Comparable<Member> { /** Listener support for incoming messages */ private MessageListenerSupport messageListenerSupport; /** The current connection handler */ private volatile ConnectionHandler peer; /** * If this node has completely handshaked. TODO: Move this into * connectionHandler ? */ private volatile boolean handshaked; /** The number of connection retries to the most recent known remote address */ private volatile int connectionRetries; /** The total number of reconnection tries at this moment */ private final AtomicInteger currentConnectTries = new AtomicInteger(0); /** his member information */ private final MemberInfo info; /** The last time, the node was seen on the network */ private Date lastNetworkConnectTime; /** Lock when peer is going to be initialized */ private final Object peerInitializeLock = new Object(); /** Folderlist waiter */ private final Object folderListWaiter = new Object(); /** * Lock to ensure that only one thread executes the folder membership * synchronization. */ private final ReentrantLock folderJoinLock = new ReentrantLock(); /** * The last message indicating that the handshake was completed */ private volatile HandshakeCompleted lastHandshakeCompleted; /** Folder memberships received? */ private volatile boolean folderListReceived; private FolderList lastFolderList; /** * The number of expected deltas to receive to have the filelist completed * on that folder. Might contain negativ values! means we received deltas * after the inital filelist. */ private Map<FolderInfo, Integer> expectedListMessages = Util .createConcurrentHashMap(); /** Last trasferstatus */ private TransferStatus lastTransferStatus; /** * the last problem */ private volatile Problem lastProblem; /** maybe we cannot connect, but member might be online */ private boolean isConnectedToNetwork; /** Flag if we received a wrong identity from remote side */ private boolean receivedWrongRemoteIdentity; /** If already asked for friendship */ private boolean askedForFriendship; /** If the remote node is a server. */ private boolean server; /** * Constructs a member using parameters from another member. nick, id , * connect address. * <p> * Attention:Does not takes friend status from memberinfo !! you have to * manually * * @param controller * Reference to the Controller * @param mInfo * memberInfo to clone */ public Member(Controller controller, MemberInfo mInfo) { super(controller); info = mInfo; } /** * @param searchString * @return if this member matches the search string or if it equals the IP * nick contains the search String * @see MemberInfo#matches(String) */ public boolean matches(String searchString) { return matches(searchString, false); } /** * @param searchString * @param matchAccount * true if the Account username should be also considerd for * matching. * @return if this member matches the search string or if it equals the IP * nick contains the search String * @see MemberInfo#matches(String) */ public boolean matches(String searchString, boolean matchAccount) { if (info.matches(searchString)) { return true; } if (!matchAccount) { return false; } AccountInfo aInfo = getAccountInfo(); if (aInfo == null) { return false; } if (aInfo.getUsername() != null && aInfo.getUsername().toLowerCase().indexOf(searchString) >= 0) { return true; } return aInfo.getDisplayName() != null && aInfo.getDisplayName().toLowerCase().indexOf(searchString) >= 0; } public String getHostName() { if (getReconnectAddress() == null) { return null; } return getReconnectAddress().getHostName(); } public String getIP() { // if (ip == null) { if (getReconnectAddress() == null || getReconnectAddress().getAddress() == null) { return null; } return getReconnectAddress().getAddress().getHostAddress(); // } // return ip; } public int getPort() { if (getReconnectAddress() == null || getReconnectAddress().getAddress() == null) { return 0; } return getReconnectAddress().getPort(); } /** * @return true if the connection to this node is secure. */ public boolean isSecure() { return peer != null && peer.isEncrypted(); } private Boolean mySelf; /** * Answers if this is myself * * @return true if this object references to "myself" else false */ public boolean isMySelf() { if (mySelf != null) { // Use cache return mySelf; } mySelf = equals(getController().getMySelf()); return mySelf; } /** * #1646 * * @return true if this computer is one of mine computers (same login). */ public boolean isMyComputer() { AccountInfo aInfo = getAccountInfo(); if (aInfo == null) { return false; } return aInfo.equals(getController().getOSClient().getAccountInfo()); } /** * Answers if this member is a friend, also true if isMySelf() * * @return true if this user is a friend or myself. */ public boolean isFriend() { return getController().getNodeManager().isFriend(this); } /** * Sets friend status of this member * * @param newFriend * The new friend status. * @param personalMessage * the personal message to send to the remote user. */ public void setFriend(boolean newFriend, String personalMessage) { boolean stateChanged = isFriend() ^ newFriend; // Inform node manager if (stateChanged) { getController().getNodeManager().friendStateChanged(this, newFriend, personalMessage); // Remove from folders. if (!newFriend && !isCompletelyConnected() && hasJoinedAnyFolder()) { for (Folder folder : getController().getFolderRepository() .getFolders(true)) { folder.remove(this); } } } } /** * Marks the node for immediate connection */ public void markForImmediateConnect() { getController().getReconnectManager().markNodeForImmediateReconnection( this); } /** * Answers if this node is interesting for us, that is defined as friends * users on LAN and has joined one of our folders. Or if its a supernode of * we are a supernode and there are still open connections slots. * * @return true if this node is interesting for us */ public boolean isInteresting() { // logFine("isOnLAN(): " + isOnLAN()); // logFine("getController().isLanOnly():" + // getController().isLanOnly()); boolean isRelay = getController().getIOProvider() .getRelayedConnectionManager().isRelay(getInfo()); boolean isServer = getController().getOSClient().isClusterServer(this); boolean isRelayOrServer = isServer || isRelay; if (getController().getNetworkingMode() == NetworkingMode.SERVERONLYMODE && !isRelayOrServer) { return false; } boolean ignoreLAN2Internet = isServer && ConfigurationEntry.SERVER_CONNECT_FROM_LAN_TO_INTERNET .getValueBoolean(getController()); if (!ignoreLAN2Internet && getController().isLanOnly() && !isOnLAN()) { return false; } // FIXME Does not work with temporary server nodes. if (isServer || isRelay) { // Always interesting is the server! // Always interesting a relay is! return true; } Identity id = getIdentity(); if (id != null) { if (Util.compareVersions("2.0.0", id.getProgramVersion())) { logWarning("Rejecting connection to old program client: " + id + " v" + id.getProgramVersion()); return false; } // FIX for #1124. Might produce problems! if (id.isPendingMessages()) { return true; } } // logFine("isFriend(): " + isFriend()); // logFine("hasJoinedAnyFolder(): " + hasJoinedAnyFolder()); if (isFriend() || isOnLAN() || hasJoinedAnyFolder()) { return true; } // Still capable of new connections? boolean conSlotAvail = !getController().getNodeManager() .maxConnectionsReached(); if (conSlotAvail && (getController().getMySelf().isSupernode() || getController() .getMySelf().isServer())) { return true; } // Try to hold connection to supernode if max connections not reached // yet. if (conSlotAvail && isSupernode()) { return getController().getNodeManager().countConnectedSupernodes() < Constants.N_SUPERNODES_TO_CONNECT; } return false; } /** * @return true if this node is currently reconnecting (outbound) or * connecting inbound */ public boolean isConnecting() { return currentConnectTries.get() > 0; } /** * Marks the node as connecting (inbound or outbound). * <P> * Make sure to unmark the connecting status * * @return the number of currently running connection tries. Should be 1 */ public int markConnecting() { int tries = currentConnectTries.incrementAndGet(); getController().getNodeManager().connectingStateChanged(this); return tries; } /** * @return the current connection tries. 0 if not longer connecting. */ public int unmarkConnecting() { int tries = currentConnectTries.decrementAndGet(); getController().getNodeManager().connectingStateChanged(this); return tries; } /** * Answers if this member has a connected peer (a open socket). To check if * a node is completey connected & handshaked see * <code>isCompletelyConnected</code> * * @see #isCompletelyConnected() * @return true if connected */ public boolean isConnected() { try { return peer != null && peer.isConnected(); } catch (Exception e) { return false; } } /** * Answers if this node is completely connected & handshaked * * @return true if connected & handshaked */ public boolean isCompletelyConnected() { return handshaked && isConnected(); } /** * Convinience method * * @return true if the node is a supernode */ public boolean isSupernode() { return info.isSupernode; } /** * Answers if this member is on the local area network. * * @return true if this member is on LAN. */ public boolean isOnLAN() { try { if (peer != null) { return peer.isOnLAN(); } if (info.getConnectAddress() == null) { return false; } InetAddress adr = info.getConnectAddress().getAddress(); if (adr == null) { return false; } return getController().getNodeManager().isOnLANorConfiguredOnLAN( adr); } catch (RuntimeException e) { logWarning("Unable to check if client is on LAN: " + this + ". " + e); return false; } } /** * To set the lan status of the member for external source * * @param onlan * new LAN status */ public void setOnLAN(boolean onlan) { if (peer != null) { peer.setOnLAN(onlan); } } /** * Answers if we received a wrong identity on reconnect * * @return true if we received a wrong identity on reconnect */ public boolean receivedWrongIdentity() { return receivedWrongRemoteIdentity; } /** * removes the peer handler, and shuts down connection */ private void shutdownPeer() { ConnectionHandler thisPeer = peer; if (thisPeer != null) { thisPeer.shutdown(); synchronized (peerInitializeLock) { peer = null; } } } /** * @return the peer of this member. */ public ConnectionHandler getPeer() { return peer; } /** * Sets the new connection handler for this member * * @param newPeer * The peer / connection handler to set * @throws InvalidIdentityException * if peer identity doesn't match this member. * @return the result of the connection attempt */ public ConnectResult setPeer(ConnectionHandler newPeer) throws InvalidIdentityException { Reject.ifNull(newPeer, "Illegal call of setPeer(null)"); if (isFiner()) { logFiner("Setting peer to " + newPeer); } Identity identity = newPeer.getIdentity(); MemberInfo remoteMemberInfo = identity != null ? identity .getMemberInfo() : null; // #1373 if (remoteMemberInfo != null && !remoteMemberInfo.isOnSameNetwork(getController())) { if (isFine()) { logFine("Closing connection to node with diffrent network ID. Our netID: " + getController().getNodeManager().getNetworkId() + ", remote netID: " + remoteMemberInfo.networkId + " on " + remoteMemberInfo); } newPeer.shutdown(); setConnectedToNetwork(false); lastProblem = new Problem("Network ID mismatch", true, Problem.NETWORK_ID_MISMATCH); throw new InvalidIdentityException( "Closing connection to node with diffrent network ID. Our netID: " + getController().getNodeManager().getNetworkId() + ", remote netID: " + remoteMemberInfo.networkId + " on " + remoteMemberInfo, newPeer); } if (!newPeer.isConnected()) { logWarning("Peer disconnected while initializing connection: " + newPeer); return ConnectResult .failure("Peer disconnected while initializing connection"); } // check if identity is valid and matches the this member if (identity == null || !identity.isValid() || !remoteMemberInfo.matches(this)) { // Wrong identity from remote side ? set our flag receivedWrongRemoteIdentity = remoteMemberInfo != null && !remoteMemberInfo.matches(this); String identityId = identity != null ? identity.getMemberInfo().id : "n/a"; // tell remote client try { newPeer.sendMessage(IdentityReply.reject("Invalid identity: " + identityId + ", expeced " + info)); } catch (ConnectionException e) { logFiner("Unable to send identity reject", e); } finally { newPeer.shutdown(); } throw new InvalidIdentityException(this + " Remote peer has wrong identity. remote ID: " + identityId + ", expected ID: " + getId(), newPeer); } // Complete low-level handshake // FIXME: Problematic situation: Now we probably accept the new peer. // Messages received from this new peer can be delivered to the // Member which not get the correct peer since "peer" field is set // later... boolean accepted = newPeer.acceptIdentity(this); if (!accepted) { // Shutdown this member newPeer.shutdown(); logFiner("Remote side did not accept our identity: " + newPeer); return ConnectResult .failure("Remote side did not accept our identity"); } if (!identity.getMemberInfo().id.equals(info.id)) { logSevere("Got wrong indentity from peer. Expected: " + info + ". got: " + identity.getMemberInfo()); newPeer.shutdown(); return ConnectResult .failure("Got wrong indentity from peer. Expected: " + info + ". got: " + identity.getMemberInfo()); } info.nick = identity.getMemberInfo().nick; // Reset the last connect time info.setLastConnectNow(); synchronized (peerInitializeLock) { ConnectionHandler oldPeer = peer; // Set the new peer peer = newPeer; // ok, we accepted, kill old peer and shutdown. if (oldPeer != null) { oldPeer.shutdown(); } } // Update infos! if (newPeer.getRemoteListenerPort() > 0) { // get the data from remote peer // connect address is his currently connected ip + his // listner port if not supernode if (newPeer.isOnLAN()) { // Supernode state no nessesary on lan // Take socket ip as reconnect address info.isSupernode = false; info.setConnectAddress(new InetSocketAddress(newPeer .getRemoteAddress().getAddress(), newPeer .getRemoteListenerPort())); } else if (identity.getMemberInfo().isSupernode) { // Remote peer is supernode, take his info, he knows // about himself (=reconnect hostname) info.isSupernode = true; info.setConnectAddress(identity.getMemberInfo() .getConnectAddress()); } else { // No supernode. take socket ip as reconnect address. info.isSupernode = false; info.setConnectAddress(new InetSocketAddress(newPeer .getRemoteAddress().getAddress(), newPeer .getRemoteListenerPort())); } } else if (!identity.isTunneled()) { // Remote peer has no listener running info.setConnectAddress(null); // Don't change the connection address on a tunneled connection. } return completeHandshake(); } /** * Calls which can only be executed with connection * * @throws ConnectionException * if not connected */ private void checkPeer() throws ConnectionException { if (!isConnected()) { shutdownPeer(); throw new ConnectionException("Not connected").with(this); } } /** * Tries to reconnect peer * * @return the result of the connection attempt. * @throws InvalidIdentityException */ public ConnectResult reconnect() throws InvalidIdentityException { return reconnect(true); } /** * Tries to reconnect peer * * @param markConnecting * true if this member should be marked as connecting. sometimes * this has already been done by calling code. * @return the result of the connection attempt. * @throws InvalidIdentityException */ public ConnectResult reconnect(boolean markConnecting) throws InvalidIdentityException { // do not reconnect if controller is not running if (!getController().isStarted()) { return ConnectResult.failure("Controller is not started"); } if (isCompletelyConnected()) { return ConnectResult.success(); } // #1334 // if (info.getConnectAddress() == null) { // return false; // } if (isFine()) { logFine("Reconnecting (tried " + connectionRetries + " time(s) to " + this + ')'); } connectionRetries++; ConnectResult connectResult; ConnectionHandler handler = null; try { // #1334 // if (info.getConnectAddress().getPort() <= 0) { // logWarning(this + " has illegal connect port " // + info.getConnectAddress().getPort()); // return false; // } // Set reconnecting state if (markConnecting) { markConnecting(); } // Re-resolve connect address String theHostname = getHostName(); // cached hostname if (isFiner()) { logFiner("Reconnect hostname to " + getNick() + " is: " + theHostname); } if (!StringUtils.isBlank(theHostname)) { info.setConnectAddress(new InetSocketAddress(theHostname, info .getConnectAddress().getPort())); } // Another check: do not reconnect if controller is not running if (!getController().isStarted()) { return ConnectResult.failure("Controller is not started"); } // Try to establish a low-level connection. handler = getController().getIOProvider() .getConnectionHandlerFactory().tryToConnect(getInfo()); connectResult = setPeer(handler); } catch (InvalidIdentityException e) { logFiner(e); // Shut down reconnect handler if (handler != null) { handler.shutdown(); } throw e; } catch (ConnectionException e) { logFine(e.getMessage()); logFiner(e); // Shut down reconnect handler if (handler != null) { handler.shutdown(); } connectResult = ConnectResult.failure(e.getMessage()); } finally { if (markConnecting) { // Was marked, unmark it. unmarkConnecting(); } } if (connectResult.isSuccess()) { setConnectedToNetwork(true); connectionRetries = 0; } else { if (isUnableToConnect() && isConnectedToNetwork) { logWarning("Unable to connect directly to " + getReconnectAddress()); // FIXME: Find a better ways setConnectedToNetwork(false); } } return connectResult; } /** * Completes the handshake between nodes. Exchanges the relevant information * * @return the result of the connection attempt. */ private ConnectResult completeHandshake() { if (!isConnected()) { return ConnectResult.failure("Not connected"); } ConnectionHandler thisPeer = peer; if (thisPeer == null) { return ConnectResult.failure("Peer is not set"); } if (getController().getOSClient().isPrimaryServer(this)) { getController().getOSClient().primaryServerConnected(this); } boolean wasHandshaked = handshaked; Identity identity = thisPeer.getIdentity(); boolean receivedFolderList = false; // #2569: Server waits for client list of folders first. if (getController().getMySelf().isServer() && identity != null && !identity.isRequestFullFolderlist()) { receivedFolderList = waitForFoldersJoin(); } synchronized (peerInitializeLock) { if (!isConnected() || identity == null) { logFine("Disconnected while completing handshake"); return ConnectResult .failure("Disconnected while completing handshake"); } // Send node informations now // Send joined folders to synchronize FolderList remoteFolderList = getLastFolderList(); Collection<FolderInfo> folders2node = getFilteredFolderList( remoteFolderList, identity.isRequestFullFolderlist()); FolderList folderList; if (getProtocolVersion() >= 106) { folderList = new FolderListExt(folders2node, peer.getRemoteMagicId()); } else { folderList = new FolderList(folders2node, peer.getRemoteMagicId()); } if (isFiner()) { logFiner("Sending CH " + folderList); } peer.sendMessagesAsynchron(folderList); } // My messages sent, now wait for his folder list. receivedFolderList = waitForFoldersJoin(); synchronized (peerInitializeLock) { if (!isConnected()) { logFine("Disconnected while completing handshake"); return ConnectResult .failure("Disconnected while completing handshake"); } if (!receivedFolderList) { if (isConnected()) { logFine("Did not receive a folder list after 60s, disconnecting"); return ConnectResult .failure("Did not receive a folder list after 60s, disconnecting (1)"); } shutdown(); return ConnectResult .failure("Did not receive a folder list after 60s, disconnecting (2)"); } if (!isConnected()) { logFine("Disconnected while waiting for folder list"); return ConnectResult .failure("Disconnected while waiting for folder list"); } } // Create request for nodelist. RequestNodeList request = getController().getNodeManager() .createDefaultNodeListRequestMessage(); boolean thisHandshakeCompleted = true; synchronized (peerInitializeLock) { if (!isConnected()) { logFine("Disconnected while completing handshake"); return ConnectResult .failure("Disconnected while completing handshake"); } if (isInteresting()) { // Send request for nodelist. peer.sendMessagesAsynchron(request); // Send our transfer status peer.sendMessagesAsynchron(getController().getTransferManager() .getStatus()); } else { logFine("Rejected, Node not interesting"); // Tell remote side try { peer.sendMessage(new Problem("You are boring", true, Problem.DO_NOT_LONGER_CONNECT)); } catch (ConnectionException e) { // Ignore } thisHandshakeCompleted = false; } } boolean acceptByConnectionHandler = peer != null && peer.acceptHandshake(); // Handshaked ? thisHandshakeCompleted = thisHandshakeCompleted && isConnected() && acceptByConnectionHandler; if (!thisHandshakeCompleted) { String message = "not handshaked: connected? " + isConnected() + ", acceptByCH? " + acceptByConnectionHandler + ", interesting? " + isInteresting() + ", peer " + peer; if (isFiner()) { logFiner(message); } shutdown(); return ConnectResult.failure(message); } List<Folder> foldersJoined = sendFilelists(); if (foldersJoined == null) { return ConnectResult.failure("Unable to send filelists to " + getNick()); } boolean ok = waitForFileLists(foldersJoined); if (!ok) { String reason = "Disconnecting. Did not receive the full filelists for " + foldersJoined.size() + " folders: " + foldersJoined; logWarning(reason); if (isFine()) { for (Folder folder : foldersJoined) { logFine("Got filelist for " + folder.getName() + " ? " + hasCompleteFileListFor(folder.getInfo())); } } shutdown(); return ConnectResult.failure(reason); } if (isFiner()) { logFiner("Got complete filelists"); } // Wait for acknowledgement from remote side if (identity.isAcknowledgesHandshakeCompletion()) { sendMessageAsynchron(new HandshakeCompleted()); long start = System.currentTimeMillis(); if (!waitForHandshakeCompletion()) { long took = System.currentTimeMillis() - start; String message = null; if (peer == null || !peer.isConnected()) { if (lastProblem == null) { message = "Peer disconnected while waiting for handshake acknownledge (or problem)"; } } else { if (lastProblem == null) { message = "Did not receive a handshake not acknownledged (or problem) by remote side after " + (int) (took / 1000) + 's'; } } shutdown(); if (message != null && isWarning()) { logWarning(message); } return ConnectResult.failure(message); } else if (isFiner()) { logFiner("Got handshake completion!!"); } } else if (peer != null && peer.isConnected()) { // Handshaked handshaked = true; } else { shutdown(); return ConnectResult.failure("Unknown reason"); } // Reset things connectionRetries = 0; if (wasHandshaked != handshaked) { // Inform nodemanger about it getController().getNodeManager().connectStateChanged(this); // Inform security manager to update account state. boolean syncFolderMemberships = !ConfigurationEntry.SERVER_DISCONNECT_SYNC_ANYWAYS .getValueBoolean(getController()); getController().getSecurityManager().nodeAccountStateChanged(this, syncFolderMemberships); } if (isInfo()) { logInfo("Connected (" + getController().getNodeManager().countConnectedNodes() + " total)"); } // Request files for (Folder folder : foldersJoined) { // Trigger filerequesting. we may want re-request files on a // folder he joined. if (folder.getSyncProfile().isAutodownload()) { getController().getFolderRepository().getFileRequestor() .triggerFileRequesting(folder.getInfo()); } if (folder.getSyncProfile().isSyncDeletion()) { folder.triggerSyncRemoteDeletedFiles(Collections .singleton(this)); } } if (getController().isDebugReports()) { // Running with debugReports enabled (which incorporates verbose // mode) // then directly request node information. sendMessageAsynchron(new RequestNodeInformation()); } if (handshaked) { return ConnectResult.success(); } else { return ConnectResult.failure("Not handshaked"); } } /** * Sends complete filelists for all folders, this node is an actual member * of. * * @return the list of actually allowed to join folder for which filelists * have been sent. */ private List<Folder> sendFilelists() { List<Folder> foldersJoined = getFoldersActuallyJoined(); List<Folder> foldersRequested = getFoldersRequestedToJoin(); if (isFine() && !foldersJoined.isEmpty()) { logFine("Joined " + foldersJoined.size() + " folders: " + foldersJoined); } else if (isFiner()) { logFiner("Joined " + foldersJoined.size() + " folders: " + foldersJoined); } for (Folder folder : foldersJoined) { // FIX for #924 folder.waitForScan(); // Send filelist of joined folders Message[] filelistMsgs = FileList.create(folder, folder.supportExternalizable(this)); for (Message message : filelistMsgs) { try { sendMessage(message); } catch (ConnectionException e) { shutdown(); return null; } } foldersRequested.remove(folder); } if (!foldersRequested.isEmpty()) { if (isFine()) { logFine("Requested join : " + foldersRequested); logFine("Actually joined: " + foldersJoined); } for (Folder folder : foldersRequested) { sendMessagesAsynchron(FileList.createEmpty(folder.getInfo(), folder.supportExternalizable(this))); } } return foldersJoined; } /** * Waits for the filelists on those folders. After a certain amount of time * it runs on a timeout if no filelists were received. Waits max 2 minutes. * * @param folders * @return true if the filelists of those folders received successfully. */ private boolean waitForFileLists(List<Folder> folders) { if (isFiner()) { logFiner("Waiting for complete fileslists..."); } // 120 minutes. Should never occur. Waiter waiter = new Waiter(1000L * 60 * 120); boolean fileListsCompleted = false; Date lastMessageReceived = null; while (!waiter.isTimeout() && isConnected()) { fileListsCompleted = true; for (Folder folder : folders) { if (!hasCompleteFileListFor(folder.getInfo())) { fileListsCompleted = false; break; } } if (fileListsCompleted) { break; } lastMessageReceived = peer != null ? peer .getLastKeepaliveMessageTime() : null; if (lastMessageReceived == null) { logSevere("Unable to check last received message date. got null while waiting for filelist"); return false; } boolean noChangeReceivedSineOneMinute = System.currentTimeMillis() - lastMessageReceived.getTime() > 1000L * 60; if (noChangeReceivedSineOneMinute) { logWarning("No message received since 1 minute while waiting for filelist"); return false; } try { waiter.waitABit(); } catch (Exception e) { return false; } } if (waiter.isTimeout()) { logSevere("Got timeout (" + (waiter.getTimoutTimeMS() / (1000 * 60)) + " minutes) while waiting for filelist"); } if (!isConnected()) { logWarning("Disconnected while waiting for filelist"); } return fileListsCompleted; } /** * Waits some time for the folder list * * @return true if list was received successfully */ private boolean waitForFoldersJoin() { synchronized (folderListWaiter) { if (!folderListReceived) { try { if (isFiner()) { logFiner("Waiting for folderlist"); } folderListWaiter.wait(60000); } catch (InterruptedException e) { logFiner(e); } } } // Wait for joinToLocalFolders folderJoinLock.lock(); folderJoinLock.unlock(); return folderListReceived; } /** * Waits some time for the handshake to be completed * * @return true if list was received successfully */ private boolean waitForHandshakeCompletion() { // 120 minutes. Should never occur. Waiter waiter = new Waiter(1000L * 60 * 120); while (!waiter.isTimeout()) { if (lastHandshakeCompleted != null && handshaked) { return true; } if (isFiner()) { logFiner("Waiting for handshake complete message"); } Date lastMessageReceived = peer != null ? peer .getLastKeepaliveMessageTime() : null; if (lastMessageReceived == null) { logFine("Unable to check last received message date. Got disconnected while waiting for handshake complete"); return false; } boolean noChangeReceivedSineOneMinute = System.currentTimeMillis() - lastMessageReceived.getTime() > 1000L * 60; if (noChangeReceivedSineOneMinute) { logWarning("No message received since 1 minute while waiting for handshake complete"); return false; } if (!isConnected()) { return false; } waiter.waitABit(); } return lastHandshakeCompleted != null && handshaked; } /** * Shuts the member and its connection down */ public void shutdown() { boolean wasHandshaked = handshaked; shutdownPeer(); // Notify waiting locks. synchronized (folderListWaiter) { folderListWaiter.notifyAll(); } folderListReceived = false; lastFolderList = null; // Disco, assume completely setConnectedToNetwork(false); handshaked = false; lastHandshakeCompleted = null; lastTransferStatus = null; expectedListMessages.clear(); messageListenerSupport = null; // Remove filelist to save memory. for (Folder folder : getFoldersActuallyJoined()) { folder.getDAO().deleteDomain(getId(), -1); } if (wasHandshaked) { // Reset the last connect time info.setLastConnectNow(); // Inform security manager to update account state. getController().getSecurityManager().nodeAccountStateChanged(this, false); // Inform nodemanger about it getController().getNodeManager().connectStateChanged(this); if (isInfo()) { logInfo("Disconnected (" + getController().getNodeManager().countConnectedNodes() + " still connected)"); } } else { // logFiner("Shutdown"); } } /** * Helper method for sending messages on peer handler. Method waits for the * sendmessagebuffer to get empty * * @param message * The message to send * @throws ConnectionException */ public void sendMessage(Message message) throws ConnectionException { checkPeer(); if (peer != null) { // wait peer.waitForEmptySendQueue(-1); // synchronized (peerInitalizeLock) { if (peer != null) { // send peer.sendMessage(message); } // } } } /** * Enque one messages for sending. code execution does not wait util message * was sent successfully * * @see PlainSocketConnectionHandler#sendMessagesAsynchron(Message[]) * @param message * the message to send */ public void sendMessageAsynchron(Message message) { if (peer != null && peer.isConnected()) { peer.sendMessagesAsynchron(message); } } /** * Enque multiple messages for sending. code execution does not wait util * message was sent successfully * * @see PlainSocketConnectionHandler#sendMessagesAsynchron(Message[]) * @param messages * the messages to send */ public void sendMessagesAsynchron(Message... messages) { if (peer != null && peer.isConnected()) { peer.sendMessagesAsynchron(messages); } } /** * Handles an incomming message from the remote peer (ConnectionHandler) * * @param message * The message to handle * @param fromPeer * the peer this message has been received from. */ public void handleMessage(final Message message, final ConnectionHandler fromPeer) { if (message == null) { throw new NullPointerException( "Unable to handle message, message is null"); } // Profile this execution. ProfilingEntry profilingEntry = null; if (Profiling.ENABLED) { profilingEntry = Profiling.start("Member.handleMessage", message .getClass().getSimpleName()); } int expectedTime = -1; long start = System.currentTimeMillis(); try { if (getController().getOSClient().isPrimaryServer(this)) { ServerClient.SERVER_HANDLE_MESSAGE_THREAD.set(true); } // related folder is filled if message is a folder related message final FolderInfo targetedFolderInfo; final Folder targetFolder; if (message instanceof FolderRelatedMessage) { targetedFolderInfo = ((FolderRelatedMessage) message).folder; if (targetedFolderInfo != null) { targetFolder = getController().getFolderRepository() .getFolder(targetedFolderInfo); } else { targetFolder = null; logSevere("Got folder message without FolderInfo: " + message); } } else { targetedFolderInfo = null; targetFolder = null; } // do all the message processing // Processing of message also should take only // a short time, because member is not able // to received any other message meanwhile ! // Identity is not handled HERE ! if (message instanceof Ping) { // TRAC #812: Answer the ping here. PONG is handled in // ConnectionHandler! Pong pong = new Pong((Ping) message); sendMessagesAsynchron(pong); expectedTime = 50; } else if (message instanceof HandshakeCompleted) { lastHandshakeCompleted = (HandshakeCompleted) message; // Notify waiting ppl handshaked = true; expectedTime = 100; } else if (message instanceof FolderList) { final FolderList fList = (FolderList) message; // #2569 if (isWarning() && fList.secretFolders != null && fList.secretFolders.length > 100 && getController().getFolderRepository().getFoldersCount() < 100) { logWarning("Received large " + fList); } Runnable r = new Runnable() { public void run() { folderJoinLock.lock(); try { lastFolderList = fList; // fList.store(Member.this); folderListReceived = true; // Send filelist only during handshake joinToLocalFolders(fList, fromPeer); // #2569: Send only "filtered" client specific // folder list. Send renewed list. ConnectionHandler thisPeer = peer; Identity identity = thisPeer != null ? thisPeer .getIdentity() : null; boolean fullList = identity != null && identity.isRequestFullFolderlist(); if (getController().getMySelf().isServer() && !fullList && thisPeer != null) { String remoteMagicId = thisPeer .getRemoteMagicId(); Collection<FolderInfo> folders2node = getFilteredFolderList( fList, fullList); FolderList myFolderList; if (getProtocolVersion() >= 106) { myFolderList = new FolderListExt( folders2node, remoteMagicId); } else { myFolderList = new FolderList(folders2node, remoteMagicId); } if (isFine()) { logFine("Sending HM " + myFolderList); } sendMessageAsynchron(myFolderList); } } finally { folderJoinLock.unlock(); } // Notify waiting ppl synchronized (folderListWaiter) { folderListWaiter.notifyAll(); } } }; getController().getIOProvider().startIO(r); expectedTime = 300; } else if (message instanceof ScanCommand) { if (targetFolder != null) { if (targetFolder.getSyncProfile().isInstantSync() || targetFolder.getSyncProfile().isPeriodicSync()) { logFiner("Remote sync command received on " + targetFolder); getController().setPaused(false); // Now trigger the scan targetFolder.recommendScanOnNextMaintenance(); getController().getFolderRepository() .triggerMaintenance(); } if (targetFolder.getSyncProfile().isAutodownload()) { getController().getFolderRepository() .getFileRequestor() .triggerFileRequesting(targetedFolderInfo); } } expectedTime = 50; } else if (message instanceof FileRequestCommand) { if (targetFolder != null) { if (targetFolder.getSyncProfile().isAutodownload()) { getController().getFolderRepository() .getFileRequestor() .triggerFileRequesting(targetedFolderInfo); } } expectedTime = 50; } else if (message instanceof FolderDBMaintCommando) { final FolderDBMaintCommando m = (FolderDBMaintCommando) message; if (targetFolder != null) { getController().getIOProvider().startIO(new Runnable() { public void run() { targetFolder .maintainFolderDB(m.getDate().getTime()); } }); } expectedTime = 50; } else if (message instanceof RequestDownload) { final RequestDownload dlReq = (RequestDownload) message; // a download is requested. Put handling in background thread // for faster processing. if (getController().isPaused()) { // Send abort logFine("Sending abort (paused) of " + dlReq.file); sendMessagesAsynchron(new AbortUpload(dlReq.file)); } else { Runnable runner = new Runnable() { public void run() { Upload ul = getController().getTransferManager() .queueUpload(Member.this, dlReq); if (ul == null && isCompletelyConnected()) { // Send abort logWarning("Sending abort of " + dlReq.file); sendMessagesAsynchron(new AbortUpload( dlReq.file)); } if (getController().isPaused()) { // Send abort logWarning("Sending abort (paused) of " + dlReq.file); sendMessagesAsynchron(new AbortUpload( dlReq.file)); } } }; getController().getIOProvider().startIO(runner); } expectedTime = 100; } else if (message instanceof DownloadQueued) { // set queued flag here, if we received status from other side DownloadQueued dlQueued = (DownloadQueued) message; Download dl = getController().getTransferManager() .getActiveDownload(this, dlQueued.file); if (dl != null) { dl.setQueued(dlQueued.file); } else if (!downloadRecentlyCompleted(dlQueued.file)) { logWarning("Remote side queued non-existant download: " + dlQueued.file); sendMessageAsynchron(new AbortDownload(dlQueued.file)); } expectedTime = 100; } else if (message instanceof AbortDownload) { AbortDownload abort = (AbortDownload) message; // Abort the upload logFine("Received " + abort + " from " + this); getController().getTransferManager().abortUpload(abort.file, this); expectedTime = 100; } else if (message instanceof AbortUpload) { AbortUpload abort = (AbortUpload) message; // Abort the upload getController().getTransferManager().abortDownload(abort.file, this); expectedTime = 100; } else if (message instanceof FileChunk) { // File chunk received FileChunk chunk = (FileChunk) message; Download d = getController().getTransferManager() .getActiveDownload(this, chunk.file); if (d != null) { d.addChunk(chunk); } else if (downloadRecentlyCompleted(chunk.file)) { sendMessageAsynchron(new AbortDownload(chunk.file)); } expectedTime = -1; } else if (message instanceof RequestNodeList) { // Nodemanager will handle that RequestNodeList request = (RequestNodeList) message; getController().getNodeManager().receivedRequestNodeList( request, this); expectedTime = 100; } else if (message instanceof KnownNodes) { KnownNodes newNodes = (KnownNodes) message; // TODO Move this code into NodeManager.receivedKnownNodes(....) // TODO This code should be done in NodeManager // This might also just be a search result and thus not include // us for (int i = 0; i < newNodes.nodes.length; i++) { MemberInfo remoteNodeInfo = newNodes.nodes[i]; if (remoteNodeInfo == null) { continue; } if (getInfo().equals(remoteNodeInfo)) { // Take his info updateInfo(remoteNodeInfo); } } // Queue arrived node list at nodemanager getController().getNodeManager().queueNewNodes(newNodes.nodes); expectedTime = 200; } else if (message instanceof RequestNodeInformation) { // send him our node information sendMessageAsynchron(new NodeInformation(getController())); expectedTime = 50; } else if (message instanceof TransferStatus) { // Hold transfer status lastTransferStatus = (TransferStatus) message; expectedTime = 50; } else if (message instanceof NodeInformation) { if (isFiner()) { logFiner("Node information received"); } if (LoggingManager.isLogToFile()) { Debug.writeNodeInformation((NodeInformation) message); } // Cache the last node information // lastNodeInformation = (NodeInformation) message; expectedTime = -1; } else if (message instanceof SettingsChange) { SettingsChange settingsChange = (SettingsChange) message; if (settingsChange.newInfo != null) { logFine(getInfo().nick + " changed nick to " + settingsChange.newInfo.nick); setNick(settingsChange.newInfo.nick); } expectedTime = 50; } else if (message instanceof FileListRequest) { // Re-Send file list to client. if (targetFolder != null) { Runnable filelistSender = new Runnable() { public void run() { if (targetFolder.hasReadPermission(Member.this)) { // FIX for #924 targetFolder.waitForScan(); // Send filelist of joined folders logInfo("Resending file list of " + targetFolder.getName() + " to " + getNick()); Message[] filelistMsgs = FileList.create( targetFolder, targetFolder .supportExternalizable(Member.this)); for (Message filelistMsg : filelistMsgs) { try { sendMessage(filelistMsg); } catch (ConnectionException e) { logWarning("Unable to send new filelist of " + targetFolder.getName() + " to " + getNick()); } } } } }; getController().getIOProvider().startIO(filelistSender); } } else if (message instanceof FileList) { final FileList remoteFileList = (FileList) message; if (isFine()) { logFine("Received new filelist. Expecting " + remoteFileList.nFollowingDeltas + " more deltas. " + message); } // Reset counter of expected filelists expectedListMessages.put(remoteFileList.folder, remoteFileList.nFollowingDeltas); if (targetFolder != null) { // Inform folder targetFolder.fileListChanged(Member.this, remoteFileList); } expectedTime = 250; } else if (message instanceof FolderFilesChanged) { final FolderFilesChanged changes = (FolderFilesChanged) message; Integer nExpected = expectedListMessages.get(changes.folder); if (nExpected == null) { logSevere("Received folder changes on " + changes.folder.name + ", but not received the full filelist"); return; } nExpected -= 1; expectedListMessages.put(changes.folder, nExpected); TransferManager tm = getController().getTransferManager(); if (changes.getFiles() != null) { for (int i = 0; i < changes.getFiles().length; i++) { FileInfo file = changes.getFiles()[i]; // TODO Optimize: Don't break if files are same. tm.abortDownload(file, this); } } if (changes.getRemoved() != null) { for (int i = 0; i < changes.getRemoved().length; i++) { FileInfo file = changes.getRemoved()[i]; // TODO Optimize: Don't break if files are same. tm.abortDownload(file, this); } } if (isFine()) { int msgs = expectedListMessages.get(targetedFolderInfo); if (msgs >= 0) { logFine("Received folder change. Expecting " + msgs + " more deltas. " + message); } else { logFine("Received folder change. Received " + (-msgs) + " additional deltas. " + message); } } if (targetFolder != null) { // Inform folder targetFolder.fileListChanged(Member.this, changes); } expectedTime = 250; } else if (message instanceof Invitation) { // Invitation to folder Invitation invitation = (Invitation) message; // Server is the only one who is allowed to send invitations // with a different invitor if (!getController().getOSClient().isPrimaryServer(this)) { // To ensure invitor is correct for all other computers invitation.setInvitor(getInfo()); } getController().invitationReceived(invitation); expectedTime = 100; } else if (message instanceof Problem) { lastProblem = (Problem) message; if (lastProblem.problemCode == Problem.DO_NOT_LONGER_CONNECT) { // Finds us boring // set unable to connect logFine("Problem received: Node reject our connection, " + "we should not longer try to connect"); // Not connected to public network setConnectedToNetwork(true); } else if (lastProblem.problemCode == Problem.DUPLICATE_CONNECTION) { logWarning("Problem received: Node thinks we have a dupe connection to him"); } else { logWarning("Problem received: " + lastProblem); } if (lastProblem.fatal) { // Shutdown shutdown(); } expectedTime = 100; } else if (message instanceof SearchNodeRequest) { // Send nodelist that matches the search. final SearchNodeRequest request = (SearchNodeRequest) message; getController().getNodeManager().receivedSearchNodeRequest( request, this); expectedTime = 50; } else if (message instanceof AddFriendNotification) { AddFriendNotification notification = (AddFriendNotification) message; getController().makeFriendship(notification.getMemberInfo()); expectedTime = 50; } else if (message instanceof Notification) { // This is the V3 friendship notification class. // V4 uses AddFriendNotification. Notification not = (Notification) message; if (not.getEvent() == null) { logWarning("Unknown event from peer"); } else { switch (not.getEvent()) { case ADDED_TO_FRIENDS : getController().makeFriendship(getInfo()); break; default : logWarning("Unhandled event: " + not.getEvent()); } } expectedTime = 50; } else if (message instanceof RequestPart) { final RequestPart pr = (RequestPart) message; Upload up = getController().getTransferManager().getUpload( Member.this, pr.getFile()); if (up != null) { // If the upload isn't broken up.enqueuePartRequest(pr); } else { sendMessageAsynchron(new AbortUpload(pr.getFile())); } expectedTime = 100; } else if (message instanceof StartUpload) { StartUpload su = (StartUpload) message; Download dl = getController().getTransferManager() .getActiveDownload(this, su.getFile()); if (dl != null) { dl.uploadStarted(su.getFile()); } else if (downloadRecentlyCompleted(su.getFile())) { logFine("Download invalid or obsolete:" + su.getFile()); sendMessageAsynchron(new AbortDownload(su.getFile())); } expectedTime = 100; } else if (message instanceof StopUpload) { StopUpload su = (StopUpload) message; Upload up = getController().getTransferManager().getUpload( this, su.getFile()); if (up != null) { // If the upload isn't broken up.stopUploadRequest(su); } expectedTime = 100; } else if (message instanceof RequestFilePartsRecord) { RequestFilePartsRecord req = (RequestFilePartsRecord) message; Upload up = getController().getTransferManager().getUpload( this, req.getFile()); if (up != null) { // If the upload isn't broken up.receivedFilePartsRecordRequest(req); } else { sendMessageAsynchron(new AbortUpload(req.getFile())); } expectedTime = 100; } else if (message instanceof ReplyFilePartsRecord) { ReplyFilePartsRecord rep = (ReplyFilePartsRecord) message; Download dl = getController().getTransferManager() .getActiveDownload(this, rep.getFile()); if (dl != null) { dl.receivedFilePartsRecord(rep.getFile(), rep.getRecord()); } else if (downloadRecentlyCompleted(rep.getFile())) { logInfo("Download not found: " + dl); sendMessageAsynchron(new AbortDownload(rep.getFile())); } expectedTime = 100; } else if (message instanceof RelayedMessage) { RelayedMessage relMsg = (RelayedMessage) message; getController().getIOProvider().getRelayedConnectionManager() .handleRelayedMessage(this, relMsg); expectedTime = -1; } else if (message instanceof UDTMessage) { getController().getIOProvider().getUDTSocketConnectionManager() .handleUDTMessage(this, (UDTMessage) message); expectedTime = 50; } else if (message instanceof FileHistoryRequest) { final FileInfo requested = ((FileHistoryRequest) message) .getFileInfo(); // No need to wait for the FileDAO to have built the FileHistory getController().getIOProvider().startIO(new Runnable() { public void run() { Folder f = getController().getFolderRepository() .getFolder(requested.getFolderInfo()); if (f == null) { logWarning("Illegal FileHistoryRequest from " + this + ": This client is not member of the folder."); return; } sendMessageAsynchron(new FileHistoryReply(f.getDAO() .getFileHistory(requested), requested)); } }); } else if (message instanceof FileHistoryReply) { getController().getFolderRepository().getFileRequestor() .receivedFileHistory((FileHistoryReply) message); } else if (message instanceof AccountStateChanged) { AccountStateChanged asc = (AccountStateChanged) message; if (isFine()) { logFine("Received: " + asc); } Member node = asc.getNode().getNode(getController(), false); if (node != null) { getController().getSecurityManager() .nodeAccountStateChanged(node, true); } asc.decreaseTTL(); if (asc.isAlive()) { // Continue broadcast. getController().getNodeManager().broadcastMessage(asc, new Filter<Member>() { // Don't send the message back to the source. public boolean accept(Member item) { return !equals(item) && !item.isServer(); } }); } } else if (message instanceof ConfigurationLoadRequest) { if (isServer()) { ConfigurationLoadRequest clr = (ConfigurationLoadRequest) message; if (!getController().getMySelf().isServer()) { ConfigurationLoader .processMessage(getController(), clr); } else if (clr.isKeyValue()) { ConfigurationLoader .processMessage(getController(), clr); } else { logWarning("Ignoring full reload config request for myself being server: " + message); } } else { logWarning("Ignoring reload config request from non server: " + message); } } else { if (isFiner()) { logFiner("Message not known to message handling code, " + "maybe handled in listener: " + message); } } // Give message to node manager getController().getNodeManager().messageReceived(this, message); // now give the message to all message listeners fireMessageToListeners(message); } finally { ServerClient.SERVER_HANDLE_MESSAGE_THREAD.set(false); Profiling.end(profilingEntry, expectedTime); long took = System.currentTimeMillis() - start; if (took > 60000) { logWarning("Handling took " + (took/1000) + "s: " + message); } } } /** * Adds a message listener * * @param aListener * The listener to add */ public void addMessageListener(MessageListener aListener) { getMessageListenerSupport().addMessageListener(aListener); } /** * Adds a message listener, which is only triggerd if a message of type * <code>messageType</code> is received. * * @param messageType * The type of messages to register too. * @param aListener * The listener to add */ public void addMessageListener(Class<?> messageType, MessageListener aListener) { getMessageListenerSupport().addMessageListener(messageType, aListener); } /** * Removes a message listener completely from this member * * @param aListener * The listener to remove */ public void removeMessageListener(MessageListener aListener) { getMessageListenerSupport().removeMessageListener(aListener); } /** * Overridden, removes message listeners. */ @Override public void removeAllListeners() { if (isFiner()) { logFiner("Removing all listeners from member. " + this); } super.removeAllListeners(); // Remove message listeners getMessageListenerSupport().removeAllListeners(); } /** * Fires a message to all message listeners * * @param message * the message to fire */ private void fireMessageToListeners(Message message) { getMessageListenerSupport().fireMessage(this, message); } private synchronized MessageListenerSupport getMessageListenerSupport() { if (messageListenerSupport == null) { messageListenerSupport = new MessageListenerSupport(this); } return messageListenerSupport; } /* * Remote group joins */ /** * Synchronizes the folder memberships on both sides */ public void synchronizeFolderMemberships() { if (isMySelf()) { return; } if (!isCompletelyConnected()) { return; } ConnectionHandler thisPeer = peer; String remoteMagicId = thisPeer != null ? thisPeer.getRemoteMagicId() : null; if (thisPeer == null || StringUtils.isBlank(remoteMagicId)) { return; } try { folderJoinLock.lock(); FolderList folderList = getLastFolderList(); if (folderList != null) { // Rejoin to local folders joinToLocalFolders(folderList, thisPeer); } else { // Hopefully we receive this later. logSevere("Unable to synchronize memberships, " + "did not received folderlist from remote"); } // Send node informations now // Send joined folders to synchronize Identity identity = thisPeer != null ? thisPeer.getIdentity() : null; boolean fullList = identity != null && identity.isRequestFullFolderlist(); Collection<FolderInfo> folders2node = getFilteredFolderList( folderList, fullList); FolderList myFolderList; if (getProtocolVersion() >= 106) { myFolderList = new FolderListExt(folders2node, remoteMagicId); } else { myFolderList = new FolderList(folders2node, remoteMagicId); } if (isFiner()) { logFiner("Sending SFM " + myFolderList); } sendMessageAsynchron(myFolderList); } finally { folderJoinLock.unlock(); } } /** * #2569: Send only "filtered" client specific folder list if myself is a * server (server->client). For client<->client, server<->server and * client->server the full list of joined folders is returned. * * @param remoteFolderList * @return */ private Collection<FolderInfo> getFilteredFolderList( FolderList remoteFolderList, boolean fullList) { Collection<FolderInfo> allFolders = getController() .getFolderRepository().getJoinedFolderInfos(); Collection<FolderInfo> folders2node = allFolders; folders2node = allFolders; ConnectionHandler thisPeer = peer; // #2569: Send "filtered" folder list if no full list is requested. if (getController().getMySelf().isServer() && !fullList && remoteFolderList != null && thisPeer != null && StringUtils.isNotBlank(thisPeer.getMyMagicId())) { String magicId = thisPeer.getMyMagicId(); folders2node = new LinkedList<FolderInfo>(); for (FolderInfo folderInfo : allFolders) { if (remoteFolderList.contains(folderInfo, magicId)) { folders2node.add(folderInfo); } } if (isFiner() && allFolders.size() != folders2node.size()) { logFiner("Generated optimized folder list: " + folders2node.size() + "/" + allFolders.size()); } } return folders2node; } /** * Joins member to all local folders which are also available on remote * peer, removes member from all local folders, if not longer member of * * @throws ConnectionException */ private void joinToLocalFolders(FolderList folderList, ConnectionHandler fromPeer) { // logWarning("joinToLocalFolders: " + folderList); folderJoinLock.lock(); try { FolderRepository repo = getController().getFolderRepository(); Set<FolderInfo> joinedFolders = new HashSet<FolderInfo>(); Collection<Folder> localFolders = repo.getFolders(); String myMagicId = fromPeer != null ? fromPeer.getMyMagicId() : null; if (fromPeer == null) { logWarning("Unable to join to local folders. peer is null/disconnected"); return; } if (StringUtils.isBlank(myMagicId)) { logSevere("Unable to join to local folders. Own magic id of peer is blank: " + peer); return; } // Process secret folders now if (folderList.secretFolders != null && folderList.secretFolders.length > 0) { // Step 1: Calculate secure folder ids for local secret folders Map<String, Folder> localSecretFolders = new HashMap<String, Folder>(); for (Folder folder : localFolders) { // Calculate id with my magic id String secureId = folder.getInfo().calculateSecureId( myMagicId); // Add to local secret folder list localSecretFolders.put(secureId, folder); } // Step 2: Check if remote side has joined one of our secret // folders for (int i = 0; i < folderList.secretFolders.length; i++) { FolderInfo secretFolder = folderList.secretFolders[i]; if (localSecretFolders.containsKey(secretFolder.id)) { Folder folder = localSecretFolders.get(secretFolder.id); // Join him into our folder if possible. if (folder.join(this)) { if (isFiner()) { logFiner("Joined " + folder); } joinedFolders.add(folder.getInfo()); if (folderList.joinedMetaFolders) { Folder metaFolder = repo .getMetaFolderForParent(folder.getInfo()); if (metaFolder != null) { if (metaFolder.join(this)) { joinedFolders.add(metaFolder.getInfo()); if (isFiner()) { logFiner("Joined meta folder: " + metaFolder); } } else { logSevere("Unable to join meta folder of " + folder); } } else { logSevere("Unable to join meta folder. Not found " + folder); } } } else { if (isFine()) { logFine(this + " did not join into: " + folder); } } } } } // ok now remove member from no longer joined folders for (Folder folder : repo.getFolders(true)) { if (!joinedFolders.contains(folder.getInfo())) { // remove this member from folder, if not on new folder folder.remove(this); } } if (!joinedFolders.isEmpty()) { if (isFine()) { logFine(getNick() + " joined " + joinedFolders.size() + " folder(s)"); } if (!isFriend() && !server) { getController().makeFriendship(getInfo()); } } } finally { folderJoinLock.unlock(); } } /* * Request to remote peer */ /** * Answers the latest received folder list * * @return the latest received folder list */ public FolderList getLastFolderList() { return lastFolderList; } /** * Answers if we received the complete filelist (+all nessesary deltas) on * that folder. * * @param foInfo * @return true if we received the complete filelist (+all nessesary deltas) * on that folder. */ public boolean hasCompleteFileListFor(FolderInfo foInfo) { Integer nUpcomingMsgs = expectedListMessages.get(foInfo); if (nUpcomingMsgs == null) { return false; } // nUpcomingMsgs might have negativ values! means we received deltas // after the inital filelist. return nUpcomingMsgs <= 0; } /** * Returns the last transfer status of this node * * @return the last transfer status of this node */ public TransferStatus getLastTransferStatus() { if (isMySelf()) { return getController().getTransferManager().getStatus(); } return lastTransferStatus; } /** * TODO Performance bottleneck. * * @return true if user joined any folder */ public boolean hasJoinedAnyFolder() { for (Folder folder : getController().getFolderRepository().getFolders()) { if (folder.hasMember(this)) { return true; } } return false; } /** * @return the list of joined folders. */ private List<Folder> getFoldersActuallyJoined() { List<Folder> joinedFolders = new LinkedList<Folder>(); for (Folder folder : getController().getFolderRepository().getFolders( true)) { if (folder.hasMember(this)) { joinedFolders.add(folder); } } return joinedFolders; } /** * @return the list folders in common. */ private List<Folder> getFoldersRequestedToJoin() { ConnectionHandler thisPeer = peer; if (thisPeer == null) { logWarning("Node disconnected while getting folders"); return Collections.emptyList(); } String magicId = thisPeer.getMyMagicId(); // TODO Think about a better way FolderList fList = getLastFolderList(); if (fList == null) { logWarning("Unable to get last folder list"); return Collections.emptyList(); } List<Folder> requestedFolders = new LinkedList<Folder>(); for (Folder folder : getController().getFolderRepository().getFolders( true)) { FolderInfo foInfo = folder.getInfo(); if (fList.joinedMetaFolders && foInfo.isMetaFolder()) { Folder parentFolder = getController().getFolderRepository() .getParentFolder(folder.getInfo()); if (parentFolder != null) { foInfo = parentFolder.getInfo(); } } if (fList.contains(foInfo, magicId)) { requestedFolders.add(folder); } } return requestedFolders; } /** * Answers if member has the file available to download. Does NOT check * version match * * @param file * the FileInfo to find at this user * @return true if this user has this file, or false if not or if no * filelist received (yet) */ public boolean hasFile(FileInfo file) { FileInfo remoteFile = getFile(file); return remoteFile != null && !remoteFile.isDeleted(); } /** * Returns the remote file info from the node. May return null if file is * not known by remote or no filelist was received yet. Does return the * internal database file if myself. * * @param file * local file * @return the fileInfo of remote side, or null */ public FileInfo getFile(FileInfo file) { if (file == null) { throw new NullPointerException("File is null"); } Folder folder = file.getFolder(getController().getFolderRepository()); if (folder == null) { // Folder not joined, so we don't have the file. return null; } if (isMySelf()) { return folder.getDAO().find(file, null); } return folder.getDAO().find(file, getId()); } /* * Simple getters */ /** * @return The ID of this member */ public String getId() { return info.id; } /** * @return nick name of the member */ public String getNick() { return info.nick; } /** * set the nick name of this member * * @param nick * The nick to set */ public void setNick(String nick) { info.nick = nick; // Fire event on nodemanager getController().getNodeManager().fireNodeSettingsChanged(this); } /** * #1373 * * @return true if this node is on the same network. */ public boolean isOnSameNetwork() { return info.isOnSameNetwork(getController()); } /** * Returns the identity of this member. * * @return the identity if connection is established, otherwise null */ public Identity getIdentity() { if (peer != null) { return peer.getIdentity(); } return null; } /** * @return #2072: the protocol version of the {@link Externalizable}s */ public int getProtocolVersion() { Identity id = getIdentity(); if (id == null) { return 0; } return id.getProtocolVersion(); } /** * @return the ip + portnumber in InetSocketAddress to connect to. */ public InetSocketAddress getReconnectAddress() { return info.getConnectAddress(); } /** * Answers when the member connected last time or null, if member never * connected * * @return Date Object representing the last connect time or null, if member * never connected */ public Date getLastConnectTime() { return info.getLastConnectTime(); } /** * Answers the last connect time of the user to the network. Last connect * time is determinded by the information about users from other nodes and * own last connection date to that node * * @return Date object representing the last time on the network */ public Date getLastNetworkConnectTime() { Date lastConnectTime = getLastConnectTime(); if (lastConnectTime == null) { return lastNetworkConnectTime; } else if (lastNetworkConnectTime == null) { return lastConnectTime; } if (lastConnectTime.after(lastNetworkConnectTime)) { return lastConnectTime; } return lastNetworkConnectTime; } /** * Returns the member information. add connected info * * @return the MemberInfo object */ public MemberInfo getInfo() { info.isConnected = isConnected(); return info; } /** * Answers if this member is connected to the PF network * * @return true if this member is connected to the PF network */ public boolean isConnectedToNetwork() { return isCompletelyConnected() || isConnectedToNetwork; } /** * set the connected to network status * * @param connected * flag indicating if this member is connected */ public void setConnectedToNetwork(boolean connected) { boolean changed = isConnectedToNetwork != connected; isConnectedToNetwork = connected; if (changed) { getController().getNodeManager() .networkConnectionStateChanged(this); } } /** * Answers if we the remote node told us not longer to connect. * * @return true if the remote side didn't want to be connected. */ public boolean isDontConnect() { return lastProblem != null && (lastProblem.problemCode == Problem.DO_NOT_LONGER_CONNECT || lastProblem.problemCode == Problem.NETWORK_ID_MISMATCH); } /** * @return true if no direct connection to this member is possible. (At * least 2 tries) */ public boolean isUnableToConnect() { return connectionRetries >= 2; } /** * @return the last problem received from this node. */ public Problem getLastProblem() { return lastProblem; } /** * @return true if this is a server that should be reconnected */ public boolean isServer() { return server; } /** * Sets/Unsets this member as server that should be reconnected. * * @param server */ public void setServer(boolean server) { boolean oldValue = this.server; this.server = server; // Notify nodemanager if (oldValue != server) { if (!server) { logFine("Not longer server: " + this); } else { logFine("Is server of cluster: " + this); } // #2569: Server 2 server connection. don't wait for folder lists if (getController().getMySelf().isServer() && server) { synchronized (folderListWaiter) { folderListWaiter.notifyAll(); } } // if (server && hasJoinedAnyFolder()) { // synchronizeFolderMemberships(); // } getController().getNodeManager().serverStateChanged(this, server); } } /** * @return the account info of the user logged in at the remote node. */ public AccountInfo getAccountInfo() { return getController().getSecurityManager().getAccountInfo(this); } /** * Updates connection information, if the other is more 'valueble'. * <p> * TODO CLEAN UP THIS MESS!!!! -> Define behaviour and write tests. * * @param newInfo * The new MemberInfo to use if more valueble * @return true if we found valueble information */ public boolean updateInfo(MemberInfo newInfo) { boolean updated = false; if (!isConnected() && newInfo.isConnected) { // take info, if this is now a supernode if (newInfo.isSupernode && !info.isSupernode) { if (isFiner()) { logFiner("Received new supernode information: " + newInfo); } } info.isSupernode = newInfo.isSupernode; info.networkId = newInfo.networkId; // Update address only if not null IP. InetSocketAddress newAddress = newInfo.getConnectAddress(); if (newAddress != null) { boolean updateConnectAddress = false; if (newAddress.isUnresolved()) { updateConnectAddress = true; } if (newAddress.getAddress() != null && !NetworkUtil.isNullIP(newAddress.getAddress())) { updateConnectAddress = true; } if (updateConnectAddress) { info.setConnectAddress(newAddress); } else if (isFiner()) { logFiner("Not updating address. Got: " + info.getConnectAddress() + ". New: " + newAddress); } } updated = true; } // Take his last connect time if newer Date newLastConnectTime = newInfo.getLastConnectTime(); boolean updateLastNetworkConnectTime = (lastNetworkConnectTime == null && newLastConnectTime != null) || (newLastConnectTime != null && lastNetworkConnectTime .before(newLastConnectTime)); if (!isConnected() && updateLastNetworkConnectTime) { // logFiner( // "Last connect time fresher on remote side. this " // + lastNetworkConnectTime + ", remote: " // + newInfo.lastConnectTime); lastNetworkConnectTime = newLastConnectTime; updated = true; } if (updated) { // Re try connection connectionRetries = 0; } return updated; } public boolean askedForFriendship() { return askedForFriendship; } public void setAskedForFriendship(boolean flag) { askedForFriendship = flag; } private boolean downloadRecentlyCompleted(FileInfo fInfo) { Reject.ifNull(fInfo, "FileInfo is null"); // Can't tell. So maybe yes! if (ConfigurationEntry.DOWNLOAD_AUTO_CLEANUP_FREQUENCY .getValueInt(getController()) == 0) { return true; } Download dl = getController().getTransferManager() .getCompletedDownload(this, fInfo); if (dl != null) { return true; } return false; } // Logger methods ********************************************************* @Override public String getLoggerName() { return super.getLoggerName() + " '" + getNick() + '\'' + (isSupernode() ? " (s)" : ""); } /* * General */ @Override public String toString() { String connect; if (isConnected()) { connect = peer + ""; } else { connect = isMySelf() ? "myself" : "-disco.-, " + "recon. at " + getReconnectAddress(); } return "Member '" + info.nick + "' (" + connect + ')'; } /** * true if the ID's of the memberInfo objects are equal * * @param other * @return true if the ID's of the memberInfo objects are equal */ @Override public boolean equals(Object other) { if (this == other) { return true; } if (other instanceof Member) { Member oM = (Member) other; return Util.equals(info.id, oM.info.id); } return false; } @Override public int hashCode() { return (info.id == null) ? 0 : info.id.hashCode(); } public int compareTo(Member m) { return info.id.compareTo(m.info.id); } }