package edu.washington.cs.oneswarm.f2f.network; import java.io.File; import java.io.FilenameFilter; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.logging.Level; import java.util.logging.Logger; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.disk.DiskManagerFileInfo; import org.gudy.azureus2.core3.download.DownloadManager; import org.gudy.azureus2.core3.download.DownloadManagerListener; import org.gudy.azureus2.core3.download.DownloadManagerPeerListener; import org.gudy.azureus2.core3.download.DownloadManagerState; import org.gudy.azureus2.core3.global.GlobalManagerListener; import org.gudy.azureus2.core3.peer.PEPeer; import org.gudy.azureus2.core3.peer.PEPeerManager; import org.gudy.azureus2.core3.peer.PEPeerSource; import org.gudy.azureus2.core3.peer.impl.PEPeerTransport; import org.gudy.azureus2.core3.torrent.TOTorrent; import org.gudy.azureus2.core3.torrent.TOTorrentException; import org.gudy.azureus2.core3.util.AENetworkClassifier; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.FileUtil; import org.gudy.azureus2.core3.util.HashWrapper; import org.gudy.azureus2.core3.util.TorrentUtils; import com.aelitis.azureus.core.AzureusCore; import com.aelitis.azureus.core.AzureusCoreFactory; import edu.uw.cse.netlab.reputation.GloballyAwareOneHopUnchoker; import edu.washington.cs.oneswarm.f2f.Log; import edu.washington.cs.oneswarm.f2f.multisource.Sha1SourceFinder; public class F2FDownloadManager { private final static String KEY_LAST_F2F_SEARCH = "KEY_LAST_F2F_SEARCH"; // don't search more often that every 10 minutes public final static long MAX_SEARCH_FREQ = 10 * 60 * 1000; private final static long MAX_FORCE_SEARCH_FREQ = 5 * 1000; private final static int SEARCH_TIMER_PERIOD = 15 * 1000; private final static int INITIAL_WAIT_PERIOD = 10 * 1000; public static boolean logToStdOut = false; private static Logger logger = Logger.getLogger(F2FDownloadManager.class.getName()); private final OverlayManager overlayManager; private final AzureusCore core; private final F2FDownloadSourceFinder sourceFinder; private final DownloadManagerListener downloadListener = new DownloadManagerListener() { public void completionChanged(DownloadManager manager, boolean completed) { } public void downloadComplete(DownloadManager manager) { // if a download manager is removed or completed, check all auto // added // download managers to see if they still are interesting sha1SourceFinder.queueSha1Tasks(); } public void filePriorityChanged(DownloadManager download, DiskManagerFileInfo file) { } public void positionChanged(DownloadManager download, int oldPosition, int newPosition) { } public void stateChanged(DownloadManager dm, int state) { if (state == DownloadManager.STATE_DOWNLOADING) { sourceFinder.sendSearch(dm, true); // if this is a normal download, check for additional // sources try { boolean f2fAllowed = isSharedWithFriends(dm.getTorrent().getHash()); boolean autoadded = dm.getDownloadState().getBooleanAttribute( Sha1SourceFinder.ONESWARM_AUTO_ADDED); boolean running = !dm.isDownloadComplete(false); boolean f2fConnected = overlayManager.getConnectCount() > 0; if (f2fAllowed && !autoadded && running && f2fConnected) { sha1SourceFinder.searchForAlternativeSources(dm); } } catch (TOTorrentException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }; private final String KEY_PEER_LISTENER = "KEY_PEER_LISTENER"; private class F2FPeerListener implements DownloadManagerPeerListener { private final DownloadManager dm; public F2FPeerListener(DownloadManager dm) { this.dm = dm; } public void peerAdded(PEPeer peer) { } public void peerManagerAdded(PEPeerManager manager) { } public void peerManagerRemoved(PEPeerManager manager) { } public void peerManagerWillBeAdded(PEPeerManager manager) { } @SuppressWarnings("unchecked") public void peerRemoved(PEPeer peer) { if (dm.getState() == DownloadManager.STATE_DOWNLOADING) { int numPeers = 0; int numSeeds = 0; List<PEPeerTransport> peers = peer.getManager().getPeers(); for (PEPeerTransport p : peers) { if (p.getPeerState() == PEPeer.TRANSFERING) { if (p.isSeed()) { numSeeds++; } else { numPeers++; } } } logger.fine("peer removed: peers/seeds: " + numPeers + "/" + numSeeds); // one or less (the removed peer is not yet removed from the // accounting in the DownloadManager if (numPeers + numSeeds <= 1) { logger.fine("F2FDownloadManager: last peer removed, allowing search again"); // allow next search in 5 s to give all the connections and // overlay transports time to disconnect and deregister dm.setData(KEY_LAST_F2F_SEARCH, System.currentTimeMillis() - MAX_SEARCH_FREQ + 5000); } } } }; private Sha1SourceFinder sha1SourceFinder; @SuppressWarnings("unchecked") public F2FDownloadManager(OverlayManager _overlayManager) { this.overlayManager = _overlayManager; this.sha1SourceFinder = new Sha1SourceFinder(overlayManager); core = AzureusCoreFactory.getSingleton(); sourceFinder = new F2FDownloadSourceFinder(); // add a listener to all existing downloads List<DownloadManager> downloads = core.getGlobalManager().getDownloadManagers(); for (DownloadManager d : downloads) { d.addListener(downloadListener); } // plus to all potential new downloads core.getGlobalManager().addListener(new GlobalManagerListener() { public void destroyInitiated() { } public void destroyed() { } public void downloadManagerAdded(DownloadManager dm) { dm.addListener(downloadListener); F2FPeerListener peerListener = new F2FPeerListener(dm); dm.addPeerListener(peerListener); dm.setData(KEY_PEER_LISTENER, peerListener); } public void downloadManagerRemoved(DownloadManager dm) { dm.removeListener(downloadListener); F2FPeerListener listener = (F2FPeerListener) dm.getData(KEY_PEER_LISTENER); if (listener != null && listener instanceof F2FPeerListener) { dm.removePeerListener(listener); } // if a download manager is removed or completed, check all auto // added // download managers to see if they still are interesting sha1SourceFinder.queueSha1Tasks(); } public void seedingStatusChanged(boolean seeding_only_mode) { } }); // finally, schedule our timertask Timer t = new Timer("F2F peer finder", true); t.schedule(sourceFinder, INITIAL_WAIT_PERIOD, SEARCH_TIMER_PERIOD); (new Timer("torrent pruning and F2F startstop", true)).schedule(new TimerTask() { public void run() { logger.fine("F2F startstop round"); Set<String> active_torrent_files = new HashSet<String>(); for (DownloadManager dm : (List<DownloadManager>) core.getGlobalManager() .getDownloadManagers()) { active_torrent_files.add(dm.getTorrentFileName()); // System.out.println("adding: " + dm.getTorrentFileName() + // " to active"); // System.out.println("considering: " + // dm.getDisplayName()); /** * things downloading (dm.getState() == * DownloadManager.STATE_DOWNLOADING ||) */ if (!(dm.getState() == DownloadManager.STATE_SEEDING)) { continue; } // nothing < 1 hr old if (dm.getDownloadState().getLongParameter( DownloadManagerState.PARAM_DOWNLOAD_ADDED_TIME) > System .currentTimeMillis() - 3600000) { continue; } // if( dm.isForceStart() ) // { // continue; // } if (dm.isDownloadComplete(false) == false) { continue; } String[] sources = dm.getDownloadState().getPeerSources(); /** * if F2F only... */ if (sources.length == 1 && sources[0].equals(PEPeerSource.PS_OSF2F)) { logger.fine("stopping f2f only: " + dm.getDisplayName()); /** * if no peers (after at least 5 minutes), stop */ if (dm.getPeerManager().getPeers().size() == 0) { dm.stopIt(DownloadManager.STATE_STOPPED, false, false); } } } logger.fine("active has: " + active_torrent_files.size()); if (logger.isLoggable(Level.FINEST)) { for (String s : active_torrent_files) { logger.finest("active: " + s); } } logger.fine("pruning torrent dir"); String configSavePath = COConfigurationManager .getStringParameter("General_sDefaultTorrent_Directory"); if (configSavePath != null) { File[] fileList = (new File(configSavePath)).listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { return name.endsWith(".torrent"); } }); if (fileList != null) { for (File torrent : fileList) { try { TOTorrent tf = TorrentUtils.readFromFile(torrent, false); if (core.getGlobalManager().getDownloadManager(tf.getHashWrapper()) != null) { continue; } if (torrent.lastModified() + (5 * 60 * 1000) > System .currentTimeMillis()) { logger.finer("skipping removal of too-new torrent: " + torrent.getName()); continue; } logger.finer("removing: " + torrent.getName()); FileUtil.deleteWithRecycle(torrent); } catch (Exception e) { e.printStackTrace(); } } } // fileList != null else { logger.info("fileList for save path: " + configSavePath + " is null, nothing to prune"); } } else { logger.info("default save path is null (nothing to prune)"); } } }, 5 * 60 * 1000, 120 * 60 * 1000); } public static boolean isSharedWithPublic(byte[] infohash) { DownloadManagerState state = getState(infohash); boolean hasPeerSource = false, hasNetwork = false; for (String source : state.getPeerSources()) { if (source.equals(PEPeerSource.PS_BT_TRACKER) || source.equals(PEPeerSource.PS_DHT)) { hasPeerSource = true; } } for (String network : state.getNetworks()) { if (network.equals(AENetworkClassifier.AT_PUBLIC)) { hasNetwork = true; break; } } return hasPeerSource && hasNetwork; } public static boolean isSharedWithFriends(byte[] infohash) { DownloadManagerState state = getState(infohash); boolean hasPeerSource = false, hasNetwork = false; for (String source : state.getPeerSources()) { if (source.equals(PEPeerSource.PS_OSF2F)) { hasPeerSource = true; break; } } for (String network : state.getNetworks()) { if (network.equals(AENetworkClassifier.AT_OSF2F)) { hasNetwork = true; break; } } return hasPeerSource && hasNetwork; } public static DownloadManagerState getState(byte[] infohash) { if (infohash == null) { Debug.out("infohash=null"); } DownloadManager dm = getDownloadManager(infohash); if (dm == null) { Debug.out("downloadmanager=null"); } DownloadManagerState state = dm.getDownloadState(); if (state == null) { logger.fine(" state=null"); } return state; } private static DownloadManager getDownloadManager(byte[] infohash) { DownloadManager dm = TorrentUtils.getDownloadManager(new HashWrapper(infohash)); if (dm == null) { logger.fine(" unknown torrent, dm=null"); } return dm; } public void setTorrentPrivacy(byte[] infohash, boolean publicNet, boolean f2fNet) { logger.fine("setting torrent privacy: pub=" + publicNet + " f2f=" + f2fNet); if (publicNet && f2fNet) { logger.fine("setTorrentPublic"); setTorrentPublic(infohash); } else if (publicNet && !f2fNet) { logger.fine("setTorrentInternetOnly"); setTorrentInternetOnly(infohash); } else if (!publicNet && f2fNet) { logger.fine("setTorrentFriendsOnly"); setTorrentFriendsOnly(infohash); } else if (!publicNet && !f2fNet) { logger.fine("setTorrentPrivate"); setTorrentPrivate(infohash); } overlayManager.getFilelistManager().scheduleFileListRefresh(); } private static void setTorrentPublic(byte[] infohash) { logger.fine("Setting torrent Public"); DownloadManagerState state = getState(infohash); // enable all peer sources for (int i = 0; i < PEPeerSource.PS_SOURCES.length; i++) { String source = PEPeerSource.PS_SOURCES[i]; state.setPeerSourceEnabled(source, true); } // enable all networks for (int i = 0; i < AENetworkClassifier.AT_NETWORKS.length; i++) { String network = AENetworkClassifier.AT_NETWORKS[i]; state.setNetworkEnabled(network, true); } } private static void setTorrentInternetOnly(byte[] infohash) { logger.fine("Setting torrent Internet Only"); DownloadManagerState state = getState(infohash); // enable all but f2f for (int i = 0; i < PEPeerSource.PS_SOURCES.length; i++) { String source = PEPeerSource.PS_SOURCES[i]; if (!source.equals(PEPeerSource.PS_OSF2F)) { state.setPeerSourceEnabled(source, true); } else { state.setPeerSourceEnabled(source, false); } } for (int i = 0; i < AENetworkClassifier.AT_NETWORKS.length; i++) { String network = AENetworkClassifier.AT_NETWORKS[i]; if (!network.equals(AENetworkClassifier.AT_OSF2F)) { state.setNetworkEnabled(network, true); } else { state.setNetworkEnabled(network, false); } } } private static void setTorrentPrivate(byte[] infohash) { logger.fine("Setting torrent Private"); DownloadManagerState state = getState(infohash); // disable all for (int i = 0; i < PEPeerSource.PS_SOURCES.length; i++) { String source = PEPeerSource.PS_SOURCES[i]; state.setPeerSourceEnabled(source, false); } for (int i = 0; i < AENetworkClassifier.AT_NETWORKS.length; i++) { String network = AENetworkClassifier.AT_NETWORKS[i]; state.setNetworkEnabled(network, false); } } private static void setTorrentFriendsOnly(byte[] infohash) { logger.fine("Setting torrent Friends Only"); DownloadManagerState state = getState(infohash); logger.fine("got state, #: " + getDownloadManager(infohash).getState()); // set the peer sources to only be f2f for (int i = 0; i < PEPeerSource.PS_SOURCES.length; i++) { String source = PEPeerSource.PS_SOURCES[i]; if (source.equals(PEPeerSource.PS_OSF2F)) { state.setPeerSourceEnabled(source, true); } else { state.setPeerSourceEnabled(source, false); } } for (int i = 0; i < AENetworkClassifier.AT_NETWORKS.length; i++) { String network = AENetworkClassifier.AT_NETWORKS[i]; if (network.equals(AENetworkClassifier.AT_OSF2F)) { state.setNetworkEnabled(network, true); } else { state.setNetworkEnabled(network, false); } } logger.fine("done setting friends only"); } // public void sendSearch(byte[] infohash) { // sourceFinder.sendSearch(getDownloadManager(infohash), true); // } class F2FDownloadSourceFinder extends TimerTask { private volatile boolean running = false; public F2FDownloadSourceFinder() { } @SuppressWarnings("unchecked") public void run() { if (!running) { try { running = true; List<DownloadManager> downloads = core.getGlobalManager().getDownloadManagers(); ArrayList<DownloadManager> toShuffle = new ArrayList<DownloadManager>(downloads); Collections.shuffle(toShuffle); logger.fine("running f2f source finder"); for (DownloadManager d : toShuffle) { sendSearch(d, false); } } finally { running = false; } } } public void sendSearch(DownloadManager d, boolean forceSearch) { try { if (d == null) { logger.warning("Not sending search (d==null"); return; } // logger.fine("processing: " + d.getDisplayName()); if (d.getState() != DownloadManager.STATE_DOWNLOADING) { // logger.fine("not sending search, state != Downloading", // logToStdOut); return; } if (overlayManager.getConnectCount() == 0) { logger.fine("no friends connected, skipping f2f search"); return; } // check if we already sent a search to this destination // within the max time limit Object l = d.getData(KEY_LAST_F2F_SEARCH); long lastSearch; if (l == null) { lastSearch = 0; } else { lastSearch = (Long) l; } boolean searchAllowed = false; if (System.currentTimeMillis() - lastSearch > MAX_SEARCH_FREQ) { searchAllowed = true; } else if (forceSearch && System.currentTimeMillis() - lastSearch > MAX_FORCE_SEARCH_FREQ) { searchAllowed = true; } if (!searchAllowed) { logger.fine("not sending search, lastTime: " + (System.currentTimeMillis() - lastSearch) + "<" + MAX_SEARCH_FREQ + " force=" + forceSearch); return; } byte[] infohash = d.getTorrent().getHash(); DownloadManagerState state = getState(infohash); // if the friend network is enabled, auto send // searches if (state.isNetworkEnabled(AENetworkClassifier.AT_OSF2F)) { overlayManager.getSearchManager().sendHashSearch(infohash); d.setData(KEY_LAST_F2F_SEARCH, System.currentTimeMillis()); logger.fine("sending F2F search: " + d.getDisplayName()); boolean autoadded = state .getBooleanAttribute(Sha1SourceFinder.ONESWARM_AUTO_ADDED); if (!autoadded) { logger.fine("sending sha1 search: " + d.getDisplayName()); sha1SourceFinder.searchForAlternativeSources(d); } } } catch (TOTorrentException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }