package edu.washington.cs.oneswarm.f2f.friends; import java.io.File; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Semaphore; import java.util.logging.Logger; import org.bouncycastle.util.encoders.Base64; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.SystemProperties; import com.aelitis.azureus.core.AzureusCore; import com.aelitis.azureus.core.AzureusCoreComponent; import com.aelitis.azureus.core.AzureusCoreException; import com.aelitis.azureus.core.AzureusCoreLifecycleListener; import com.aelitis.azureus.core.impl.AzureusCoreImpl; import edu.washington.cs.oneswarm.f2f.Friend; import edu.washington.cs.oneswarm.f2f.OSF2FMain; import edu.washington.cs.oneswarm.f2f.dht.DHTConnector; import edu.washington.cs.oneswarm.f2f.permissions.PermissionsDAO; import edu.washington.cs.oneswarm.f2f.xml.OSF2FXMLBeanReader; import edu.washington.cs.oneswarm.f2f.xml.OSF2FXMLBeanReader.OSF2FXMLBeanReaderCallback; import edu.washington.cs.oneswarm.f2f.xml.OSF2FXMLBeanWriter; import edu.washington.cs.publickey.PublicKeyFriend; public class FriendManager { static Logger logger = Logger.getLogger(FriendManager.class.getName()); public static File OSF2F_DIR; public static String OSF2F_DIR_NAME = "osf2f"; private final static String OSF2F_FRIEND_FILE = "osf2f.friends"; static { OSF2F_DIR = new File(SystemProperties.getUserPath() + File.separator + OSF2F_DIR_NAME + File.separator); } private final ClassLoader cl; private final Semaphore diskSemaphore = new Semaphore(1); private final FriendImportManager friendImportManager; private final ConcurrentHashMap<FriendKey, Friend> friends; private final Set<String> ignoreFutureFriendRequestsFrom = Collections .synchronizedSet(new HashSet<String>()); private final Map<FriendKey, Long> nonFriendConnectionAttempts = Collections .synchronizedMap(new LinkedHashMap<FriendKey, Long>() { private static final int MAX_ENTRIES = 20; private static final long serialVersionUID = 1L; @Override protected boolean removeEldestEntry(Map.Entry<FriendKey, Long> eldest) { return size() > MAX_ENTRIES; } }); private final byte[] ownPublicKey; private volatile boolean readCompleted = false; public FriendManager(ClassLoader classLoader, byte[] ownPublicKey) { this.ownPublicKey = ownPublicKey; cl = classLoader; friends = new ConcurrentHashMap<FriendKey, Friend>(); readFromDisk(); registerShutdownHook(); friendImportManager = new FriendImportManager(this); Timer timer = new Timer("friend write timer", true); timer.schedule(new TimerTask() { @Override public void run() { flushToDisk(false, true, false); } }, 5 * 60 * 1000, 2 * 60 * 1000); } public int addFriend(Friend newFriend) { if (newFriend == null) { logger.warning("tried to add friend (but friend null)"); (new Exception()).printStackTrace(); return 0; } Friend[] newFriends = { newFriend }; return this.addFriend(newFriends); } public int addFriend(Friend[] newFriends) { if (newFriends == null) { Debug.out("tried to add friend (but friend null)"); return 0; } logger.finer("adding " + newFriends.length + " friends"); int numAdded = 0; for (Friend osF2FFriend : newFriends) { if (!Arrays.equals(ownPublicKey, osF2FFriend.getPublicKey())) { FriendKey fk = new FriendKey(osF2FFriend.getPublicKey()); Friend existingFriend = friends.get(fk); if (existingFriend == null || existingFriend.isBlocked()) { numAdded++; if (osF2FFriend.getDateAdded() == null) { osF2FFriend.setDateAdded(new Date()); } else if (osF2FFriend.getDateAdded().equals(new Date(0))) { osF2FFriend.setDateAdded(new Date()); } friends.put(fk, osF2FFriend); /** * PIAMOD -- syncing with the groups code here */ PermissionsDAO.get().refresh_friend_groups(); if (!osF2FFriend.isBlocked()) { // trigger a dht lookup of the friend DHTConnector friendConnector = OSF2FMain.getSingelton().getDHTConnector(); if (friendConnector != null) { try { friendConnector.publishLocationInfoForFriend(osF2FFriend); } catch (Exception e) { Debug.out("problem when trying to publish friend location in dht", e); } friendConnector.connectToFriend(osF2FFriend); } else { Debug.out("friend connector=null"); } } } else { /* * maybe we have new ip:port info for the friend, try to * connect */ if (osF2FFriend.getLastConnectIP() != null && osF2FFriend.getLastConnectPort() > 0) { existingFriend.setLastConnectIP(osF2FFriend.getLastConnectIP()); existingFriend.setLastConnectPort(osF2FFriend.getLastConnectPort()); // trigger a dht lookup of the friend DHTConnector friendConnector = OSF2FMain.getSingelton().getDHTConnector(); if (friendConnector != null) { try { friendConnector.publishLocationInfoForFriend(osF2FFriend); } catch (Exception e) { Debug.out("problem when trying to publish friend location in dht", e); } friendConnector.connectToFriend(osF2FFriend); } } } } else { Debug.out("tried to add myself as friend"); } } if (numAdded > 0) { this.flushToDisk(true, false, false); } logger.fine("added friends:" + numAdded); return numAdded; } public void addToIgnoreRequestList(String publicKey) { ignoreFutureFriendRequestsFrom.add((publicKey)); } /** * Converts a list of friends from PublicKeyFriends objects to Friend * objects, if the friend name is null they are skipped */ public List<Friend> convertToFriendArrayAndFilter(List<PublicKeyFriend> importedFriends) { LinkedList<Friend> newFriends = new LinkedList<Friend>(); for (PublicKeyFriend publicKeyFriend : importedFriends) { /* * skip if we can't map the uid to a real name */ if (publicKeyFriend.getRealName() == null) { Debug.out("could not get real name for keynick: " + publicKeyFriend.getKeyNick() + " , this should not happen unless you are using multiple xmpp accounts"); continue; } /* * skip if the public key is null */ byte[] publicKey = publicKeyFriend.getPublicKey(); if (publicKey == null) { continue; } /* * skip if the user is the local machine */ if (Arrays.equals(publicKey, ownPublicKey)) { continue; } FriendKey key = new FriendKey(publicKey); /* * skip if we are already friends with the user */ if (friends.get(key) != null) { continue; } /* * ok, all is good, add to list */ String nick = publicKeyFriend.getRealName() + " (" + publicKeyFriend.getKeyNick() + ")"; boolean canSeeFilelist = false; Friend newFriend = new Friend(publicKeyFriend.getSourceNetwork().getNetworkName(), nick, publicKey, canSeeFilelist); newFriend.setRequestFileList(false); newFriend.setBlocked(true); newFriend.setNewFriend(true); newFriends.add(newFriend); } return newFriends; } public List<Friend> filterKnownFriends(List<Friend> allFriends) { LinkedList<Friend> newFriends = new LinkedList<Friend>(); for (Friend f : allFriends) { byte[] publicKey = f.getPublicKey(); if (publicKey != null && !Arrays.equals(publicKey, ownPublicKey)) { FriendKey key = new FriendKey(publicKey); if (friends.get(key) == null) { newFriends.add(f); } } } return newFriends; } public void flushToDisk(final boolean makeBackup, final boolean block, final boolean allowDecreasedSize) { // check if we are running already if (!diskSemaphore.tryAcquire()) { // ok, don't sweat it, lets skip this one since the friend flush // thread will write this to disk every 5 min anyway return; } else { // ok, permits are available, lets release it diskSemaphore.release(); } FriendBean[] b = new FriendBean[friends.size()]; Friend[] f = friends.values().toArray(new Friend[friends.size()]); for (int i = 0; i < b.length; i++) { b[i] = new FriendBean(f[i]); } Thread t = new Thread(new OSF2FXMLBeanWriter<FriendBean>(b, diskSemaphore, OSF2F_FRIEND_FILE, makeBackup, allowDecreasedSize)); t.setName("OSF2FXMLBeanWriter flushToDisk()"); t.setContextClassLoader(cl); t.start(); if (block) { try { t.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } logger.finest("wrote " + b.length + " friends to disk"); } public Map<byte[], String> getDeniedIncomingConnections() { Map<byte[], String> m = new HashMap<byte[], String>(); synchronized (nonFriendConnectionAttempts) { for (FriendKey key : nonFriendConnectionAttempts.keySet()) { m.put(key.getPublicKey(), key.ip); } } return m; } public Friend getFriend(byte[] publicKey) { if (publicKey == null) { (new Exception()).printStackTrace(); logger.warning("getFriend() called with null key"); return null; } this.waitForRead(); return friends.get(new FriendKey(publicKey)); } public Friend getFriend(FriendBean bean) { try { Date lastConnectDate = null; if (bean.getLastConnectDate() != 0) { lastConnectDate = new Date(bean.getLastConnectDate()); } Friend created = new Friend(bean.isCanSeeFileList(), bean.isAllowChat(), new Date( bean.getDateAdded()), lastConnectDate, InetAddress.getByName(bean .getLastConnectIP()), bean.getLastConnectPort(), bean.getNick(), Base64.decode(bean.getPublicKey()), bean.getSourceNetwork(), bean.getTotalDownloaded(), bean.getTotalUploaded(), bean.isBlocked(), bean.isNewFriend()); // fix this bug for old friends that didn't have it properly set. if (created.getDateAdded() == null) { created.setDateAdded(new Date()); } else if (created.getDateAdded().equals(new Date(0))) { created.setDateAdded(new Date()); } created.setRequestFileList(bean.isRequestFileList()); // don't want to change the constructor... created.setGroup(bean.getGroup()); if (bean.getDhtReadLocation() != null) { created.setDhtReadLocation(Base64.decode(bean.getDhtReadLocation())); } if (bean.getDhtWriteLocation() != null) { created.setDhtWriteLocation(Base64.decode(bean.getDhtWriteLocation())); } created.setDhtLocationConfirmed(bean.isDhtLocationConfirmed()); return created; } catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } // /** // * New friends are added in the blocked state // * // * @param sourceNet // * @param importedFriends // * @return // */ // public Friend[] getFriends() { this.waitForRead(); Friend[] f = new Friend[friends.size()]; return friends.values().toArray(f); } public List<byte[]> getKnownKeysForFriendImport() { List<byte[]> knownKeys = new LinkedList<byte[]>(); for (Friend f : friends.values()) { /* * filter friends added manually from getting sent, we don't want * the server to know about those */ if (!f.getSourceNetwork().equals("Manual") && !f.getSourceNetwork().equals("Lan")) { knownKeys.add(f.getPublicKey()); } } knownKeys.add(ownPublicKey); return knownKeys; } // public List<Friend> getNewFriends() { // LinkedList<Friend> newFriends = new LinkedList<Friend>(); // for (Friend f : friends.values()) { // if (f.isNewFriend()) { // newFriends.add(f); // } // } // return newFriends; // } public Map<String, Integer> getNewFriendsCountsFromAutoCheck() { List<PublicKeyFriend> newFriends = friendImportManager.getNewFriends(); List<Friend> filtered = convertToFriendArrayAndFilter(newFriends); Map<String, Integer> countUsersPerNetwork = new HashMap<String, Integer>(); for (Friend friend : filtered) { if (!isOnIgnoreRequestList(new String(Base64.encode(friend.getPublicKey())))) { String network = friend.getSourceNetwork(); if (!countUsersPerNetwork.containsKey(network)) { countUsersPerNetwork.put(network, 0); } // Log.log("New friend from autocheck: " + ) countUsersPerNetwork.put(network, countUsersPerNetwork.get(network) + 1); } } logger.finest("returning new friends from autocheck: " + countUsersPerNetwork); return countUsersPerNetwork; } public void gotConnectionFromNonFriend(String ip, byte[] remotePub) { FriendKey key = new FriendKey(remotePub); key.ip = ip; nonFriendConnectionAttempts.put(key, System.currentTimeMillis()); } public boolean hasTriedToConnect(Friend f) { return nonFriendConnectionAttempts.containsKey(new FriendKey(f.getPublicKey())); } public boolean isOnIgnoreRequestList(String publickey) { return ignoreFutureFriendRequestsFrom.contains((publickey)); } private void readFromDisk() { try { logger.finer("reading friend file from disk, waiting for disk semaphore"); diskSemaphore.acquire(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } OSF2FXMLBeanReader<FriendBean> reader = new OSF2FXMLBeanReader<FriendBean>(cl, FriendBean.class, OSF2F_FRIEND_FILE, diskSemaphore, new OSF2FXMLBeanReaderCallback<FriendBean>() { @Override public void readObject(FriendBean object) { Friend friend = getFriend(object); FriendKey fk = new FriendKey(friend.getPublicKey()); friends.put(fk, friend); } @Override public void completed() { // update community server friends to not request file // list, can // be removed in 2010... logger.finest("checking for initial file list requests settings"); handleFileListRequestInitial(); logger.finer("friend read completed"); } private void handleFileListRequestInitial() { String param = "osf2f_friend_file_list_request_initial"; if (!COConfigurationManager.hasParameter(param, true)) { for (Friend f : friends.values()) { // default to limited=don't request the file // list f.setRequestFileList(f.isCanSeeFileList()); } logger.finest("updated friends, saving..."); flushToDisk(true, true, false); COConfigurationManager.setParameter(param, new Boolean(true)); } } }); // Thread t = new Thread(reader); // t.setDaemon(true); // t.start(); logger.finer("reading friend file from disk, reading"); try { reader.run(); } catch (Exception e) { // TODO: Attempt recovery from backup. e.printStackTrace(); } logger.finer("reading friend file from disk, completed"); } private void registerShutdownHook() { AzureusCoreImpl.getSingleton().addLifecycleListener(new AzureusCoreLifecycleListener() { @Override public void componentCreated(AzureusCore core, AzureusCoreComponent component) { // TODO Auto-generated method stub } @Override public boolean restartRequested(AzureusCore core) throws AzureusCoreException { // TODO Auto-generated method stub return false; } @Override public void started(AzureusCore core) { // TODO Auto-generated method stub } @Override public void stopped(AzureusCore core) { // TODO Auto-generated method stub } @Override public void stopping(AzureusCore core) { logger.fine("stopping, "); flushToDisk(false, true, false); } @Override public boolean stopRequested(AzureusCore core) throws AzureusCoreException { // System.out // .println("stop requested, flushing friends to disk"); // flushToDisk(false); return true; } @Override public boolean syncInvokeRequired() { // TODO Auto-generated method stub return false; } }); } public void removeFriend(byte[] publicKey) { friends.remove(new FriendKey(publicKey)); boolean makeBackup = true; boolean block = true; this.flushToDisk(makeBackup, block, true); /** * PIAMOD -- syncing with the groups code here */ PermissionsDAO.get().refresh_friend_groups(); } private void waitForRead() { if (!readCompleted) { try { diskSemaphore.acquire(); diskSemaphore.release(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } readCompleted = true; } } private class FriendKey { private int hash = 0; private String ip; private final byte[] publicKey; public FriendKey(byte[] publicKey) { this.publicKey = publicKey; } @Override public boolean equals(Object obj) { if (obj instanceof FriendKey) { FriendKey comp = (FriendKey) obj; if (Arrays.equals(comp.getPublicKey(), this.getPublicKey())) { return true; } } return false; } public byte[] getPublicKey() { return publicKey; } @Override public int hashCode() { if (hash == 0) { hash = Arrays.hashCode(publicKey); } return hash; } } }