/* * Created on Feb 24, 2005 * Created by Alon Rohter * Copyright (C) 2004-2005 Aelitis, All Rights Reserved. * * 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. * * AELITIS, SARL au capital de 30,000 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. * */ package com.aelitis.azureus.plugins.chat.peer.impl; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.gudy.azureus2.plugins.PluginInterface; import org.gudy.azureus2.plugins.download.*; import org.gudy.azureus2.plugins.messaging.*; import org.gudy.azureus2.plugins.network.IncomingMessageQueueListener; import org.gudy.azureus2.plugins.peers.*; import com.aelitis.azureus.plugins.chat.ChatPlugin; import com.aelitis.azureus.plugins.chat.messaging.MessageListener; import com.aelitis.azureus.plugins.chat.peer.PeerController; import com.aelitis.azureus.plugins.chat.peer.impl.messaging.CMMessage; import com.aelitis.azureus.plugins.chat.peer.impl.messaging.CMNoRoute; import com.aelitis.azureus.plugins.chat.peer.impl.messaging.CMRoute; import com.aelitis.azureus.plugins.chat.peer.impl.messaging.ChatMessage; /** * */ public class PeerControllerImpl implements PeerController { private final int NB_MAX_HOPS = 10; private final ChatPlugin chat_plugin; private Map downloadsToLastMessages; private static final int NB_LAST_MESSAGES = 512; // maps download -> list of routers private Map downloadsToRouters; private static final int NB_ROUTERS_PER_TORRENT = 5; // maps download -> list of peers to route to private Map downloadsToRoutePeer; // maps download -> list of peers private Map downloadsToPeers; // Messages Listeners private List listeners; //Ignores private List nickIgnores; private List idIgnores; public PeerControllerImpl( ChatPlugin plugin ) { this.chat_plugin = plugin; downloadsToLastMessages = new HashMap(); downloadsToRouters = new HashMap(); downloadsToRoutePeer = new HashMap(); downloadsToPeers = new HashMap(); listeners = new ArrayList(); nickIgnores = new LinkedList(); idIgnores = new LinkedList(); } public void initialize() { try { chat_plugin.getPluginInterface().getMessageManager().registerMessageType( new CMMessage( "",new byte[20],-1, "" ) ); chat_plugin.getPluginInterface().getMessageManager().registerMessageType( new CMNoRoute() ); chat_plugin.getPluginInterface().getMessageManager().registerMessageType( new CMRoute() ); } catch( MessageException e ) { e.printStackTrace(); } } public void startPeerProcessing() { PluginInterface pi = chat_plugin.getPluginInterface(); pi.getDownloadManager().addListener(new DownloadManagerListener() { public void downloadAdded( Download dwnld ) { downloadsToLastMessages.put(dwnld, new LinkedList()); downloadsToRoutePeer.put(dwnld, new LinkedList()); downloadsToRouters.put(dwnld,new ArrayList()); downloadsToPeers.put(dwnld,new ArrayList()); notifyListenersOfDownloadAdded(dwnld); notifyListenersOfDownloadInactive(dwnld); } public void downloadRemoved( Download download ) { notifyListenersOfDownloadRemoved(download); downloadsToLastMessages.remove(download); downloadsToPeers.remove(download); downloadsToRoutePeer.remove(download); downloadsToRouters.remove(download); } }); pi.getMessageManager().locateCompatiblePeers( pi, new CMMessage("",new byte[20],0,""), new MessageManagerListener() { public void compatiblePeerFound(Download download, Peer peer, Message message) { messagingPeerFound( download, peer ); } public void peerRemoved(Download download, Peer peer) { notifyOfPeerRemoval( download, peer ); } }); } private void messagingPeerFound( final Download download, final Peer peer ) { //Add the peer to the list of peers List peers = (List) downloadsToPeers.get(download); if(peers != null) { synchronized(peers) { if(peers.size() == 0) { notifyListenersOfDownloadActive(download); } peers.add(peer); } } //register for incoming JPC message handling peer.getConnection().getIncomingMessageQueue().registerListener( new IncomingMessageQueueListener() { public boolean messageReceived( Message message ) { if( message.getID().equals( ChatMessage.ID_CHAT_MESSAGE ) ) { //System.out.println( "Received [" +message.getDescription()+ "] message from peer [" +peer.getClient()+ " @" +peer.getIp()+ ":" +peer.getPort()+ "]" ); CMMessage msg = (CMMessage)message; processMessage(download,peer,msg); return true; } if( message.getID().equals( ChatMessage.ID_CHAT_NO_ROUTE ) ) { //System.out.println( "Received [" +message.getDescription()+ "] message from peer [" +peer.getClient()+ " @" +peer.getIp()+ ":" +peer.getPort()+ "]" ); processNoRoute(download,peer); return true; } if( message.getID().equals( ChatMessage.ID_CHAT_ROUTE ) ) { //System.out.println( "Received [" +message.getDescription()+ "] message from peer [" +peer.getClient()+ " @" +peer.getIp()+ ":" +peer.getPort()+ "]" ); //CMRoute route = (CMRoute)message; processRoute(download,peer); return true; } return false; } public void bytesReceived( int byte_count ) { /*nothing*/ } }); //Peers start as "non routing", ie none of the 2 newly connected peers should //route any message to the other. //If not enough "routers" are used, add this peer as a router //and send him a message about it //If enough peers are routers, randomly check if we should remove the oldest one //and use that new one List routers = (List) downloadsToRouters.get(download); synchronized (routers) { if(routers.size() < NB_ROUTERS_PER_TORRENT) { routers.add(peer); peer.getConnection().getOutgoingMessageQueue().sendMessage(new CMRoute()); } else { int acceptLevel = (int) (100 * NB_ROUTERS_PER_TORRENT / peers.size()); if(Math.random() * 100 < acceptLevel) { Peer oldPeer = (Peer) routers.remove(0); oldPeer.getConnection().getOutgoingMessageQueue().sendMessage(new CMNoRoute()); routers.add(peer); } } } } private void notifyOfPeerRemoval( final Download download, final Peer peer ) { List routePeers = (List) downloadsToRoutePeer.get(download); if(routePeers != null) { synchronized(routePeers) { routePeers.remove(peer); } } //Remove the peer to the list of peers List peers = (List) downloadsToPeers.get(download); if(peers != null) { synchronized(peers) { if(peers.remove(peer) && peers.size() == 0) { notifyListenersOfDownloadInactive(download); } } } List routers = (List) downloadsToRouters.get(download); if(routers.contains(peer)) { synchronized(routers) { routers.remove(peer); } //A router is dropping, we need to find a new peer to //route us the messages synchronized(peers) { List peersCopy = new ArrayList(peers); peersCopy.removeAll(routers); if(peersCopy.size() > 0) { int random = (int) (Math.random() * peersCopy.size()); Peer peersToAskRoute =(Peer) peersCopy.get(random); peersToAskRoute.getConnection().getOutgoingMessageQueue().sendMessage(new CMRoute()); } } } } private void processMessage(Download download,Peer peer,CMMessage message) { // 1. Test if the message has already been processed int messageID = message.getMessageID(); List lastMessages = (List) downloadsToLastMessages.get(download); synchronized(lastMessages) { if(lastMessages.contains(new Integer(messageID))) { //Do nothing, duplicate } else { //Add it to the queue of messages received lastMessages.add(0,new Integer(messageID)); //If the queue is too long, drop the last item if(lastMessages.size() > NB_LAST_MESSAGES) lastMessages.remove(lastMessages.size() - 1); //New message :) byte[] peerID = message.getSenderID(); String nick = message.getSenderNick(); //Check out that it's not an ignored peer if(nickIgnores.contains(nick)) { nickIgnores.remove(nick); idIgnores.add(peerID); } boolean ignore = false; Iterator iterIgnore = idIgnores.iterator(); while(iterIgnore.hasNext()) { byte[] id = (byte[]) iterIgnore.next(); if(compareIDs(peerID,id)) { ignore = true; } } if(ignore) return; //Check if Nick doesn't override with our nick if(!compareIDs(download.getDownloadPeerId(),peerID) && nick.equals(chat_plugin.getNick())) { sendMessage(download,download.getDownloadPeerId(),"System","/me : Multiple peers are using the nick " + nick); } String text = message.getText(); notifyListenersOfMessageReceived(download,peerID,nick,text); //Dispatch the message List routePeers = (List) downloadsToRoutePeer.get(download); int nbHops = message.getNbHops() + 1; if(nbHops < NB_MAX_HOPS) { synchronized(routePeers) { Iterator iter = routePeers.iterator(); while(iter.hasNext()) { Peer peerToRoute = (Peer) iter.next(); //Don't send it to the sending peer byte[] peerToRouteID = peerToRoute.getId(); if(peerToRoute != peer && ! compareIDs(peerID,peerToRouteID)) { CMMessage msg = new CMMessage(messageID,nick,peerID,nbHops,text); peerToRoute.getConnection().getOutgoingMessageQueue().sendMessage(msg); } } } } } } } private synchronized void processNoRoute(Download download,Peer peer) { List routePeers = (List) downloadsToRoutePeer.get(download); synchronized (routePeers) { routePeers.remove(peer); } } private synchronized void processRoute(Download download,Peer peer) { List routePeers = (List) downloadsToRoutePeer.get(download); synchronized (routePeers) { if(!routePeers.contains(peer)) { routePeers.add(peer); } } } public void addMessageListener(MessageListener listener) { synchronized (listeners) { listeners.add(listener); } } public void removeMessageListener(MessageListener listener) { synchronized (listeners) { listeners.remove(listener); } } private void notifyListenersOfMessageReceived(Download download,byte[] peerID, String nick, String text) { synchronized (listeners) { for(Iterator iter = listeners.iterator() ; iter.hasNext() ; ) { MessageListener listener = (MessageListener) iter.next(); listener.messageReceived(download,peerID,nick,text); } } } private void notifyListenersOfDownloadAdded(Download download) { synchronized (listeners) { for(Iterator iter = listeners.iterator() ; iter.hasNext() ; ) { MessageListener listener = (MessageListener) iter.next(); listener.downloadAdded(download); } } } private void notifyListenersOfDownloadRemoved(Download download) { synchronized (listeners) { for(Iterator iter = listeners.iterator() ; iter.hasNext() ; ) { MessageListener listener = (MessageListener) iter.next(); listener.downloadRemoved(download); } } } private void notifyListenersOfDownloadActive(Download download) { synchronized (listeners) { for(Iterator iter = listeners.iterator() ; iter.hasNext() ; ) { MessageListener listener = (MessageListener) iter.next(); listener.downloadActive(download); } } } private void notifyListenersOfDownloadInactive(Download download) { synchronized (listeners) { for(Iterator iter = listeners.iterator() ; iter.hasNext() ; ) { MessageListener listener = (MessageListener) iter.next(); listener.downloadInactive(download); } } } public void sendMessage(String nick,String message) { if(downloadsToPeers == null) return; synchronized (downloadsToPeers) { Iterator iter = downloadsToPeers.keySet().iterator(); while(iter.hasNext()) { Download download = (Download) iter.next(); sendMessage(download,download.getDownloadPeerId(),nick,message); } } } private String oldNick; public void sendMessage(Download download,byte[] peerID, String nick, String message) { sendMessage(download,peerID,nick,message,true); } public void sendMessage(Download download,byte[] peerID, String nick, String message,boolean checkForNick) { if(checkForNick && ! nick.equals("System") && oldNick != null && ! oldNick.equals(nick)) { sendMessage(download,peerID,"System","/me : " + oldNick + " is now known as " + nick); } if(! nick.equals("System")) oldNick = nick; notifyListenersOfMessageReceived(download,download.getDownloadPeerId(),nick,message); List routePeers = (List) downloadsToPeers.get(download); if(routePeers != null) { synchronized (routePeers) { CMMessage msg = new CMMessage(nick,peerID,0,message); for(Iterator iter = routePeers.iterator(); iter.hasNext() ;) { Peer peerToSendMsg = (Peer) iter.next(); CMMessage msgToSend = new CMMessage(msg.getMessageID(),nick,peerID,0,message); peerToSendMsg.getConnection().getOutgoingMessageQueue().sendMessage(msgToSend); } } } } private boolean compareIDs(byte[] id1, byte[] id2) { if(id1 == null) return id2 == null; if(id2 == null) return false; if(id1.length != id2.length) return false; for(int i = id1.length - 1 ; i >= 0 ; i--) { if(id1[i] != id2[i]) return false; } return true; } public boolean isDownloadActive(Download download) { List peers = (List) downloadsToPeers.get(download); if(peers == null) return false; return peers.size() > 0; } public void ignore(String nick) { nickIgnores.add(nick); } }