// Commented for the Learning branch
package com.limegroup.bittorrent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.limegroup.gnutella.http.HTTPHeaderName;
import com.limegroup.gnutella.http.HttpClientManager;
import com.limegroup.gnutella.io.NBThrottle;
import com.limegroup.gnutella.io.NIOSocket;
import com.limegroup.gnutella.io.Throttle;
import com.limegroup.gnutella.settings.ApplicationSettings;
import com.limegroup.gnutella.*;
import com.limegroup.bittorrent.handshaking.IncomingBTHandshaker;
import com.limegroup.bittorrent.settings.BittorrentSettings;
import com.limegroup.gnutella.settings.ConnectionSettings;
import com.limegroup.gnutella.settings.DownloadSettings;
import com.limegroup.gnutella.settings.UploadSettings;
import com.limegroup.bittorrent.BTMetaInfo;
import com.limegroup.bittorrent.ManagedTorrent;
import com.limegroup.gnutella.util.CommonUtils;
import com.limegroup.gnutella.util.ConverterObjectInputStream;
import com.limegroup.gnutella.util.FileUtils;
/**
* The program's single TorrentManager object keeps a list of the torrents we're sharing.
*
* === Getting a torrent ===
*
* To get a torrent, call one of the 3 download() methods.
* download(URL) takes the address to a .torrent file on the Web.
* download(File) takes the path to a .torrent file saved on the disk.
* download(byte[]) tatkes the contents of a .torrent file.
*
* === Slots and lists ===
*
* TorrentManager keeps 2 lists of ManagedTorrent objects.
* They are named _active and _waiting.
* We're actively sharing the torrents in the _active list online.
* Those in the _waiting list are not being shared.
*
* The private getMaxActiveTorrents() method determines how many slots we have.
* It looks at what kind of Internet connection the user told the settings wizard.
* If the user specified a fast internet connection, it will return 6.
*
* This means that the program will share up to 6 torrents at a time.
* The ManagedTorrent objects for these torrents will be in the _active list.
* If the user adds a 7th torrent, it will go into the _waiting list.
*
* The torrents in the _waiting list don't get cycled into the _active list.
* But, if the user pauses or removes one of the active torrents, the program will move one from waiting to active.
*
* === Our BitTorrent peer ID ===
*
* TorrentManager contains the program's BitTorrent peer ID.
* These are the 20 bytes that uniquely identify us among BitTorrent programs.
* We'll tell them to a remote computer in the BitTorrent handshake.
* They are like "LIMEguidguidguidguid", with our vendor code followed by our Gnutella client ID.
* To get them, call getPeerId().
*
* === Saving the list ===
*
* The program saves 2 files in LimeWire's "Incomplete" directory, torrents.dat and torrents.bak.
* If we have trouble reading torrents.dat, we'll try torrents.bak instead.
* The files contain an ArrayList of BTMetaInfo objects that Java serialized.
* A BTMetaInfo object represents the bencoded data inside a .torrent file.
* writeSnapshot() puts the torrents from both the active and waiting lists in the file.
*/
public class TorrentManager implements ConnectionAcceptor {
/** Not used by code in this class. */
private static final Throttle UPLOAD_THROTTLE = new NBThrottle(true, DownloadSettings.MAX_DOWNLOAD_BYTES_PER_SEC.getValue());
/**
* Our BitTorrent peer ID, the 20 bytes that uniquely identify us amongst other BitTorrent programs.
*
* Composed like "LIMEguidguidguidguid".
* The first 4 bytes are "LIME", our vendor code.
* The 16 bytes after that are are Gnutella client ID GUID.
*
* BitTorrent programs exchange peer IDs as the last 20 bytes of the handshake.
*/
private final byte[] PEER_ID;
/** A debugging log we can save lines of text to as the program runs. */
private static final Log LOG = LogFactory.getLog(TorrentManager.class);
/** 1 minute in milliseconds, we'll save our list of torrents to downloads.dat every minute. */
private int SNAPSHOT_CHECKPOINT_TIME = 60 * 1000;
/**
* The torrents the program is sharing online.
* A LinkedList of ManagedTorrent objects.
*/
private List _active = new LinkedList();
/**
* The torrents the program has, but isn't sharing online, because we're already sharing 6 other torrents.
* A LinkedList of ManagedTorrent objects.
*/
private List _waiting = new LinkedList();
/** How fast we're uploading data for all of our torrents right now. */
private float _currentUpload;
/** How fast we're downloading data for all of our torrents right now. */
private float _currentDownload;
/** The total average upload speed for our torrents. */
private float _averageUpload;
/** The total average download speed for our torrents. */
private float _averageDownload;
/** The number of measurements we took to calculate our average upload and download speeds. */
private int _numMeasures;
/** A link up to the ActivityCallback, which lets us talk to the GUI. */
private ActivityCallback _callback;
/**
* Make the program's single TorrentManager object, which keeps our list of torrents.
* RouterService calls this, and saves the new object as torrentManager.
*
* Composes our BitTorrent peer ID as the 20 bytes "LIME[client ID GUID]".
*/
public TorrentManager() {
// Get our Gnutella client ID GUID, which uniquely identifies us on the Gnutella network
String clientId = ApplicationSettings.CLIENT_ID.getValue(); // The program chose it the first time it ran on this computer, and saved it in settings
byte[] guid; // Turn it into 16 bytes
if (clientId.length() != 0 && clientId != null) guid = GUID.fromHexString(clientId);
else guid = GUID.makeGuid();
// Compose our BitTorrent peer ID, the 20 bytes that will uniquely identify us among BitTorrent programs
PEER_ID = new byte[20];
String qhdVendorName = CommonUtils.QHD_VENDOR_NAME; // Make the first 4 bytes "LIME"
PEER_ID[0] = (byte) qhdVendorName.charAt(0);
PEER_ID[1] = (byte) qhdVendorName.charAt(1);
PEER_ID[2] = (byte) qhdVendorName.charAt(2);
PEER_ID[3] = (byte) qhdVendorName.charAt(3);
System.arraycopy(guid, 0, PEER_ID, 4, 16); // Copy in the 16 byts of our client ID GUID after that
// Make a note the program's TorrentManager is ready to go
if (LOG.isDebugEnabled()) LOG.debug("TorrentManager created");
}
/**
* Open torrents.dat, schedule repeating tasks, and register the BitTorrent handshake greeting with the ConnectionDispatcher.
* Only RouterService.start() calls this method.
*
* First, initialize() opens torrents.dat, the file the program saved in LimeWire's "Incomplete" folder the last time it ran.
* If we can't read torrents.dat, we read torrents.bak instead.
* Inside the file is an ArrayList of BTMetaInfo objects.
* The readSnapshot() method makes a ManagedTorrent for each one, and adds them to our list.
*
* initialize() schedules 2 Runnable anonymous inner classes with the RouterService.
* Every minute, code here will save our list of torrents to the torrents.dat file.
* Every 10 seconds, code here will see if we have an open slot for a torrent, and start sharing a new one in it.
*
* This method reigsters the TorrentManager with the ConnectionDispatcher as a ConnectionAcceptor.
* When a remote computer contacts the program's listening socket and says "#BitTorrent", we'll get the connection.
* This is the start of the BitTorrent handshake, with the first byte, #, holding the number 19.
*/
public void initialize() {
// Make a note we're here
if (LOG.isDebugEnabled()) LOG.debug("initializing TorrentManager");
// Get a link up to the ActivityCallback, that will let us talk to the GUI
_callback = RouterService.getCallback();
// Get the paths of 2 files in LimeWire's "Incomplete" folder, torrents.dat, and its backup, torrents.bak
File real = BittorrentSettings.TORRENT_SNAPSHOT_FILE.getValue(); // torrents.dat
File backup = BittorrentSettings.TORRENT_SNAPSHOT_BACKUP_FILE.getValue(); // torrents.bak
/*
* Try once with the real file, then with the backup file.
*/
// Read the BTMetaInfo objects in torrents.dat, make ManagedTorrent objects for each, and add them to our list
if (!readSnapshot(real)) {
// readShapshot() returns false if there was an error reading the file
if (LOG.isDebugEnabled()) LOG.debug("Reading real torrents.dat failed");
// Try torrents.bak instead
if (readSnapshot(backup)) {
// That worked, copy torrents.bak to torrents.dat
if (LOG.isDebugEnabled()) LOG.debug("Reading backup torrents.bak succeeded.");
copyBackupToReal();
// We couldn't read torrents.dat or torrents.bak, but one or both of the files are there
} else if (backup.exists() || real.exists()) {
// Show the user an error
if (LOG.isDebugEnabled()) LOG.debug("Reading both torrents files failed.");
MessageService.showError("TORRENTS_COULD_NOT_READ_SNAPSHOT");
}
// We read torrents.dat
} else {
// Make a note that it worked
if (LOG.isDebugEnabled()) LOG.debug("Reading torrents.dat worked!");
}
// Have the RouterService run the code here every minute
Runnable checkpointer = new Runnable() {
public void run() {
// Only save something if we have some torrents right now
if (_active.size() > 0) {
// Write the torrents we're sharing to torrents.dat
if (!writeSnapshot()) {
// It didn't work, copy torrents.bak to torrents.dat to restore a copy that might work
copyBackupToReal();
}
}
}
};
RouterService.schedule(checkpointer, SNAPSHOT_CHECKPOINT_TIME, SNAPSHOT_CHECKPOINT_TIME); // 1 minute from now, and every minute after that
// Have the RouterService run the code here every 10 seconds
Runnable waitingPimp = new Runnable() {
public void run() {
// If we have a slot for another torrent, start sharing it
wakeUp();
}
};
RouterService.schedule(waitingPimp, 10 * 1000, 10 * 1000); // 10 seconds from now, and every 10 seconds after that
/*
* Register the TorrentManager as a ConnectionAcceptor.
* When a remote computer connects to our listening socket and says "#BitTorrent", we'll get the connection.
*
* The BitTorrent handshake begins "#BitTorrent protocol".
* The first byte, #, has the value 19.
* After that are the 19 ASCII text characters "BitTorrent protocol".
*
* We'll just give the first part, "#BitTorrent", to the ConnectionDispatcher.
* It will read this much from the socket, realize the remote computer wants BitTorrent, and give the connection to us.
*/
// Compose the greeting "#BitTorrent" in a StringBuffer
StringBuffer word = new StringBuffer();
word.append((char)19); // The first byte has the value 19
word.append("BitTorrent"); // After that are the 19 ASCII characters "BitTorrent protocol"
// Register this object with the ConnectionDispatcher as a ConnectionAcceptor
RouterService.getConnectionDispatcher().addConnectionAcceptor(
this, // A link to this object, the program's TorrentManager
new String[]{ word.toString() }, // The greeting to look for, "#BitTorrent"
false, // Not local only, let Internet computers contact us
false); // Not blocking, we're using NIO
}
/**
* Copy torrents.bak to torrents.dat.
* This restores a backup of our torrents.dat file.
*/
private synchronized void copyBackupToReal() {
// Make a note we're doing this
if (LOG.isDebugEnabled()) LOG.debug("copying backup file to main saving file");
// Get the paths to both files in Java File objects
File real = BittorrentSettings.TORRENT_SNAPSHOT_FILE.getValue(); // The path to the torrents.dat file
File backup = BittorrentSettings.TORRENT_SNAPSHOT_BACKUP_FILE.getValue(); // The path to the torrents.bak file
// Replace torrents.dat with torrents.bak
real.delete(); // Delete torrents.dat
CommonUtils.copy(backup, real); // Rename torrents.bak to torrents.dat
}
/**
* Write the torrents we're sharinng to torrents.dat.
* Call this as the program runs to save what we're doing.
*
* Renames torrents.dat to torrents.bak, creating a backup.
* Writes the contents of the .torrent files we're sharing to torrents.dat.
*
* The files is an ArrayList of BTMetaInfo objects.
*
* @return true if we successfully saved the file.
* false if we weren't able to.
*/
public synchronized boolean writeSnapshot() {
// Make a note we're writing the file that lists our torrents
LOG.debug("writing snapshot");
// Make a list for the .torrent files we're still downloading
List buf = new ArrayList();
// Loop for each torrent we're sharing on the network
for (int i = 0; i < _active.size(); i++) {
ManagedTorrent mt = (ManagedTorrent) _active.get(i);
// If we haven't finished downloading it yet, add the information from its .torrent file to our list
if (!mt.isComplete()) buf.add(mt.getMetaInfo());
}
// Loop for each torrent that's waiting to be shared
for (int i = 0; i < _waiting.size(); i++) {
ManagedTorrent mt = (ManagedTorrent) _waiting.get(i);
// If we haven't finished downloading it yet, add the information from its .torrent file to our list
if (!mt.isComplete()) buf.add(mt.getMetaInfo());
}
// Get the path to our torrents.dat file in LimeWire's "Incomplete" folder
File outFile = BittorrentSettings.TORRENT_SNAPSHOT_FILE.getValue();
// Delete torrents.bak, and rename torrents.dat to torrents.bak, creating the new backup
BittorrentSettings.TORRENT_SNAPSHOT_BACKUP_FILE.getValue().delete();
outFile.renameTo(BittorrentSettings.TORRENT_SNAPSHOT_BACKUP_FILE.getValue());
// Write list of BTMetaInfo.
try {
// Open torrents.dat for writing, and make a new ObjectOutputStream that we can give objects to for it to serialize them to the file
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(BittorrentSettings.TORRENT_SNAPSHOT_FILE.getValue()));
try {
// Give it the ArrayList of BTMetaInfo objects
out.writeObject(buf);
out.flush(); // Have it write to the file now
// If that worked with an exception, record success and return true
if (LOG.isDebugEnabled()) LOG.debug("snapshot written");
return true;
// Run this code before leaving the try block
} finally {
// Close the file
out.close();
}
// There was a problem writing torrents.dat
} catch (IOException e) {
// Restore the backup by renaming torrents.bak to torrents.dat
if (!FileUtils.forceRename(BittorrentSettings.TORRENT_SNAPSHOT_BACKUP_FILE.getValue(), BittorrentSettings.TORRENT_SNAPSHOT_FILE.getValue())) {
// That didn't work
ErrorService.error(e, "could not save torrents.dat file, backup failed, please restart LimeWire.");
}
// Report that we didn't save anything
if (LOG.isDebugEnabled()) LOG.debug("snapshot not written", e);
return false;
}
}
/**
* Read the file the program saved its list of torrents in the last time.
*
* The file is an ArrayList of BTMetaInfo objects.
* Makes a ManagedTorrent object for each one, and adds it to our list, and the GUI's list of downloads.
*
* Reads the torrents serialized in TORRENT_SNAPSHOT_FILE and adds them to
* this, queued. The queued torrents will restart immediately if slots are
* available. Returns false iff the file could not be read for any reason.
* THIS METHOD SHOULD BE CALLED BEFORE ANY GUI ACTION. It is public for
* testing purposes only!
*
* @param file The torrents.dat or torrents.bat file we can open and read
* @return true if it worked, false on error
*/
public synchronized boolean readSnapshot(File file) {
// Make a note that we're going to read the file
if (LOG.isDebugEnabled()) LOG.debug("reading Snapshot");
// The ArrayList we'll read from inside the file
List buf = null;
try {
/*
* This does not try to maintain backwards compatibility with older
* versions of LimeWire, which only wrote the list of torrents.
* This doesn't really cause an errors, however.
*/
// Open the file, read the contents, and turn it into a Java ArrayList object
ObjectInputStream in = new ConverterObjectInputStream(new FileInputStream(file));
buf = (List)in.readObject();
// If there is an exception, catch it, make a note about it in the debugging log, and return false
} catch (IOException e) {
LOG.debug(e);
return false;
} catch (ClassCastException e) {
LOG.debug(e);
return false;
} catch (ClassNotFoundException e) {
LOG.debug(e);
return false;
} catch (ArrayStoreException e) {
LOG.debug(e);
return false;
} catch (IndexOutOfBoundsException e) {
LOG.debug(e);
return false;
} catch (NegativeArraySizeException e) {
LOG.debug(e);
return false;
} catch (IllegalStateException e) {
LOG.debug(e);
return false;
} catch (SecurityException e) {
LOG.debug(e);
return false;
}
/*
* Initialize and start torrents. Must catch ClassCastException since
* the data could be corrupt.
*/
try {
// Loop for each BTMetaInfo object in the file
for (Iterator iter = buf.iterator(); iter.hasNext(); ) {
BTMetaInfo info = (BTMetaInfo)iter.next();
// Make a new ManagedTorrent from it
ManagedTorrent torrent = new ManagedTorrent(info, this);
// Add the ManagedTorrent to our list, and start sharing it
addTorrent(torrent);
// List the torrent as a download in LimeWire's GUI
_callback.addDownload(torrent.getDownloader());
}
// Report success
if (LOG.isDebugEnabled()) LOG.debug("snapshot read");
return true;
// The data was corrupt
} catch (ClassCastException e) {
// Report error
return false;
}
}
/**
* Add a given torrent to the list of them the TorrentManager keeps.
* If we're not sharing our maximum number of torrents right now, we'll start sharing this new one right away.
* If we are at our maximum, we'll add it to a list of torrents waiting to be shared when others are removed or paused.
*
* @param mt The ManagedTorrent object that represents a torrent to start sharing
*/
public synchronized void addTorrent(ManagedTorrent mt) {
// If we already have the given ManagedTorrent in our active or waiting lists, don't add it again
if (_active.contains(mt) || _waiting.contains(mt)) return;
// We're already sharing our maximum number of torrents
if (_active.size() >= getMaxActiveTorrents()) {
// Add the given torrent to the waiting list
_waiting.add(mt);
if (LOG.isDebugEnabled()) LOG.debug("torrent added to waiting");
// We have room to share another torrent
} else {
// Start sharing it, and add it to the active list
mt.start();
if (LOG.isDebugEnabled()) LOG.debug("torrent added to active");
_active.add(mt);
}
// Tell the GUI to add the torrent to the list of Gnutella downloads
_callback.addDownload(mt.getDownloader());
}
/**
* Determine if a torrent we're sharing should go offline to let a waiting torrent that's not done yet finish.
* Looks for an incomplete torrent in our waiting list.
*
* @return true if there's an incomplete torrent in our waiting list.
* false if our waiting list is empty, or all the torrents there are done.
*/
public synchronized boolean shouldYield() {
// Loop for each of our torrents we're not sharing online because we're already sharing 6 other ones
for (Iterator iter = _waiting.iterator(); iter.hasNext(); ) {
ManagedTorrent m2 = (ManagedTorrent)iter.next();
// If this one isn't finished downloading yet, return true, we're out of slots to finish getting a torrent
if (!m2.isComplete()) return true;
}
// No, all of our waiting torrents are done, or we don't have any waiting torrents
return false;
}
/**
* Download a .torrent file from a given Web address, add it to our list, and start getting the torrent.
* This method blocks while we download the .torrent file, for up to 8 seconds.
*
* @param url A Java URL object that has the address to the .torrent file on the Web
* @return A BTDownloader object that lets the GUI list the download
* @throws IOException If there is a problem downloading the file
*/
public synchronized Downloader download(URL url) throws IOException {
// Make a note in the debugging log
if (LOG.isDebugEnabled()) LOG.debug("downloading torrent from " + url);
// Download the .torrent file
HttpMethod get = new GetMethod(url.toExternalForm());
get.addRequestHeader("User-Agent", CommonUtils.getHttpServer());
get.addRequestHeader(HTTPHeaderName.CONNECTION.httpStringValue(), "close");
get.setFollowRedirects(true);
HttpClient http = HttpClientManager.getNewClient(Constants.TIMEOUT, Constants.TIMEOUT);
http.executeMethod(get); // Control blocks here while the HttpMethod object downloads the file
if (get.getStatusCode() < 200 || get.getStatusCode() >= 300) throw new IOException("bad status code, downloading .torrent file " + get.getStatusCode());
// Add the torrent to our list, and return its BTDownloader object that will let the GUI list it
return download(get.getResponseBody()); // The response body is the contents of the .torrent file
}
/**
* Add a new torrent to our list, and start downloading it.
*
* @param torrentFile A Java File object with the path to a .torrent file saved on this computer
* @return A BTDownloader object that lets the GUI list the download
*/
public synchronized Downloader download(File torrentFile) throws IOException {
/*
* The single line of code does the following things:
* readFileFully() reads the .torrent file from the disk, and returns its contents as a byte array.
* download() takes that byte array, makes a ManagedTorrent, and returns its BTDownloader object.
*/
// Add the torrent to our list, and return its BTDownloader object that will let the GUI list it
return download(FileUtils.readFileFully(torrentFile));
}
/**
* Add a new torrent to our list, and start downloading it.
*
* Parses the contents of the .torrent file into a BTMetaInfo object.
* Uses that to make a new ManagedTorrent object that represents the torrent.
* Returns the ManagedTorrent's BTDownload object, which lets the GUI list the download.
*
* @param torrentFile A byte array with the contents of a .torrent file we downloaded or opened from disk
* @return A BTDownloader object that will let the GUI list this download
*/
public synchronized Downloader download(byte[] torrentFile) throws IOException {
// Make a note in the debugging log
if (LOG.isDebugEnabled()) LOG.debug("trying to open torrent");
try {
// Make a new BTMetaInfo object from the data of the .torrent file
BTMetaInfo info = BTMetaInfo.readFromBytes(torrentFile);
// Loop for each of our torrents
List buf = new ArrayList(); // Make a new ArrayList
buf.addAll(_active); // Add our active torrents, and
buf.addAll(_waiting); // Add the ones we have paused
for (Iterator iter = buf.iterator(); iter.hasNext(); ) {
ManagedTorrent torrent = (ManagedTorrent)iter.next();
// We alredy have the given torrent
if (Arrays.equals(info.getInfoHash(), torrent.getInfoHash())) {
// Get all of its trackers
for (int i = 0; i < info.getTrackers().length; i++) torrent.getMetaInfo().addTracker(info.getTrackers()[i]);
// Don't start a new download, just return the BTDownloader we already have
return torrent.getDownloader();
}
}
// Make a new ManagedTorrent object to represent the torrent
ManagedTorrent mt = new ManagedTorrent(info, this);
// Add it to our list, and start sharing it
addTorrent(mt);
// Return its BTDownloader that can communicate results to the GUI
return mt.getDownloader();
// BTMetaInfo.readFromBytes() found an error in the .torrent file
} catch (IOException e) {
// Make a note, and then throw the exception as though we didn't catch it
if (LOG.isDebugEnabled()) LOG.debug("bad torrent file", e);
throw e;
}
}
/**
* Stop sharing a torrent, or remove it from the program entirely.
*
* @param mt The ManagedTorrent to remove
* @param clear true to completely remove the torrent from the program.
* false to just stop sharing it online right now.
*/
public synchronized void removeTorrent(ManagedTorrent mt, boolean clear) {
// If we don't have the given torrent, there is nothing for us to remove
if (!_active.contains(mt) && !_waiting.contains(mt)) return;
// Remove it from the _active or _waiting lists, whichever it is in right now
_active.remove(mt); // This tries to remove it from both, even though it is only in one list and not the other
_waiting.remove(mt);
// If the caller doesn't want to completely remove the torrent from the program
if (!clear) {
// Add it to the waiting list
_waiting.add(mt); // If this opened up a slot, it will move back onto the active list as soon as we call wakeUp() below
}
// Write the torrents we're sharing to torrents.dat
writeSnapshot();
// Share up to 6 torrents at once
wakeUp();
}
/**
* Get our BitTorrent peer ID, the 20 bytes that uniquely identify us amongst other BitTorrent programs.
* BitTorrent programs exchange peer IDs as the last 20 bytes of the handshake.
*
* Our peer ID is a 20-byte array like "LIMEguidguidguidguid".
* The first 4 bytes are "LIME", our vendor code.
* The 16 bytes after that are are Gnutella client ID GUID.
*
* @return Our BitTorrent peer ID in a 20-byte array
*/
public byte[] getPeerId() {
// Return the peer ID our constructor composed
return PEER_ID;
}
/**
* Start sharing the given torrent if we're not already sharing 6 of them.
* Only ManagedTorrent.resume() calls this method.
*
* @param torrent The ManagedTorrent the user wants to start sharing
*/
public synchronized void wakeUp(ManagedTorrent torrent) {
// If we're sharing fewer torrents than settings limit, and the given one is in our waiting list
if (_active.size() < getMaxActiveTorrents() && _waiting.contains(torrent)) {
// Move it from the _waiting list to the _active list, and start sharing it online
_waiting.remove(torrent);
_active.add(torrent); // Open the files on the disk, contact the tracker, and connect to peers
torrent.start();
}
}
/**
* Share up to 6 torrents at once.
* Moves ManagedTorrent objects from our _waiting list to our _active list, and calls start() on them.
*
* The RouterService calls this method every 10 seconds.
* removeTorrent() also calls this method.
*/
public synchronized void wakeUp() {
/*
* we definitely need some kind of bandwidth throttle that can be
* applied to both HTTP and torrent uploads. The easiest way to
* achieve this would probably be to convert HTTP uploads to use NIO.
*/
// Does nothing, this line of code isn't connected to anything that matters yet
UPLOAD_THROTTLE.limit((int)UploadManager.getUploadSpeed()); // By default, the user has not limited the upload speed, and getUploadSpeed() returns no limit
// Get an iterator that will let us loop for each of our torrents waiting to be shared
Iterator iter = _waiting.iterator();
// Loop until _active is full or _waiting is empty
while (
_active.size() < getMaxActiveTorrents() && // If we're sharing fewer torrents online than settings allow right now, and
iter.hasNext()) { // We have a torrent in the _waiting list
ManagedTorrent mt = (ManagedTorrent)iter.next(); // Get the torrent in the _waiting list
// If this torrent hasn't given up, hasn't had a disk problem, and isn't paused by the user
if (mt.getState() != Downloader.GAVE_UP && mt.getState() != Downloader.DISK_PROBLEM && mt.getState() != Downloader.PAUSED) {
// Move it from the _waiting list to the _active list, and start sharing it online
_active.add(mt);
mt.start(); // Open the files on the disk, contact the tracker, and connect to peers
iter.remove();
}
}
}
/**
* Stop sharing all our torrents.
* Disconnects from peers and tells the trackers "event=stop".
*/
public synchronized void shutdown() {
// Loop through all the torrent we're sharing online right now
for (Iterator iter = _active.iterator(); iter.hasNext(); ) {
// Call stop() on each one, removing it from the network and the program
((ManagedTorrent)iter.next()).stop();
}
}
/**
* Find out how many torrents we're sharing online right now.
*
* we will give torrents slight preference over http downloaders by reducing
* the number of allowed http downloads by the number of active torrents.
*
* @return The number of ManagedTorrent objects in the TorrentManager's _active list
*/
public synchronized int getNumActiveTorrents() {
// Return the number of ManagedTorrent objects in the TorrentManager's _active list
return _active.size();
}
/**
* Not used.
*
* Have the SimpleBandwidthTracker objects in each of our MangaedTorrent objects update the speeds they keep current.
* Call this measureBandwidth() method repetedly.
*/
public synchronized void measureBandwidth() {
float currentTotalUp, currentTotalDown;
currentTotalDown = currentTotalUp = 0.f;
boolean shouldCountAvg = false;
for (Iterator iter = _active.iterator(); iter.hasNext(); ) {
shouldCountAvg = true;
ManagedTorrent mt = (ManagedTorrent)iter.next();
mt.getUploader().measureBandwidth();
mt.getDownloader().measureBandwidth();
currentTotalDown += mt.getDownloader().getMeasuredBandwidth();
currentTotalUp += mt.getUploader().getMeasuredBandwidth();
}
if (shouldCountAvg) {
_averageDownload = (_averageDownload * _numMeasures + currentTotalDown) / (_numMeasures + 1);
_averageUpload = (_averageUpload * _numMeasures + currentTotalUp) / (_numMeasures + 1);
_numMeasures++;
}
_currentDownload = currentTotalDown;
_currentUpload = currentTotalUp;
}
/** Not used. */
public float getCurrentDownload() {
return _currentDownload;
}
/** Not used. */
public float getAverageDownload() {
return _averageDownload;
}
/** Not used. */
public float getCurrentUpload() {
return _currentUpload;
}
/** Not used. */
public float getAverageUpload() {
return _averageUpload;
}
/**
* Find out where in our queue of torrents a given one is.
*
* @param to A ManagedTorrent object.
* @return 0 if we're sharing the given torrent online right now.
* 1 or more if it's in the waiting list, depending on its position in the list.
*/
public synchronized int getPositionInQueue(ManagedTorrent to) {
// If we're sharing the given torrent right now, return 0
if (_active.contains(to)) return 0; // It's not waiting in line at all
// Otherwise, return it's position in the waiting list
return _waiting.indexOf(to) + 1; // Add 1 because the first ManagedTorrent in _waiting has index 0
}
/**
* Get the non-blocking throttle the TorrentManager made to keep us from uploading data too quickly.
*
* @return Our NBThrottle object
*/
public Throttle getUploadThrottle() {
// Get the NBThrottle this object made
return UPLOAD_THROTTLE;
}
/**
* Find out how many torrents we should share at once.
* Returns a number like 1, 2, 4, or 6, depending on what kind of Internet connection the user told settings we have.
*
* @return The number of torrents we should share
*/
private int getMaxActiveTorrents() {
// Get the kind of connection the user told the setup wizard when we first ran
int speed = ConnectionSettings.CONNECTION_SPEED.getValue();
// Sort by the kind of connection
if (speed <= SpeedConstants.MODEM_SPEED_INT) return 1; // Modem, only share 1 torrent at a time
else if (speed <= SpeedConstants.CABLE_SPEED_INT) return 2; // Cable Internet, share 2 torrents at once
else if (speed <= SpeedConstants.T1_SPEED_INT) return 4; // 1.5 Mbps or more, share 4 torrents
else return 6; // Unlimited, share 6 torrents
}
/**
* Look up a ManagedTorrent by the torrent's info hash.
* Determine if we're sharing a torrent online right now.
*
* IncomingBTHandshaker.verifyIncoming() calls this method.
* A remote computer has connected to us.
* It's told us the info hash of the torrent it wants to share with us.
* We need to see if we have that torrent.
* If we don't, we'll close the connection.
*
* @param infoHash The 20-byte SHA1 hash of the "info" bencoded dictionary of the .torrent file.
* @return The ManagedTorrent object that represents that torrent we have and are sharing.
* null if we don't have that torrent, or we do but we're not sharing it online right now.
*/
public ManagedTorrent getTorrentForHash(byte[] infoHash) {
synchronized (this) {
// Loop through the torrents we're sharing online right now
for (Iterator iter = _active.iterator(); iter.hasNext(); ) {
ManagedTorrent current = (ManagedTorrent) iter.next();
// If this one has the given info hash, return it
if (Arrays.equals(infoHash, current.getInfoHash())) return current;
}
}
// No match found
return null;
}
/**
* Read the BitTorrent handshake from a remote computer that just connected to us, and respond with our own.
*
* The "NIODispatch" thread calls this acceptConnection() method when a remote computer connects to us.
* The remote computer said "[19]BitTorrent ", so the ConnectionDispatcher knew to hand the new connection to the TorrentManager.
*
* @param word The word the ConnectionDispatcher already read from the channel to determine what the remote computer wants
* @param sock The open connection socket to the remote computer we can communicate with the remote computer through
*/
public void acceptConnection(String word, Socket sock) {
// Make a new IncomingBTHandshaker object to do the BitTorrent handshake with the remote computer
IncomingBTHandshaker shaker = new IncomingBTHandshaker((NIOSocket)sock, this); // Just saves the given objects
shaker.startHandshaking(); // Makes buffers to hold the incoming data, and registers shaker with NIO
}
}