OpenNapDownload.java
| Index Score | ||
|---|---|---|
![]() |
![]() |
org.xnap.plugin.opennap.net |
![]() |
![]() |
XNap 3 |
View: Reasons, Metrics, Source Code
These are the metrics that contribute to the Enerjy Score for this file, ranked by impact. So the metrics listed at the top influence the score to a greater extent that the metrics listed at the bottom.
/*
* XNap - A P2P framework and client.
*
* See the file AUTHORS for copyright information.
*
* 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.
*
* 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
*/
package org.xnap.plugin.opennap.net;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import java.util.Hashtable;
import javax.swing.Action;
import org.apache.log4j.Logger;
import org.xnap.XNap;
import org.xnap.event.StateEvent;
import org.xnap.event.StateListener;
import org.xnap.gui.util.IconHelper;
import org.xnap.peer.Peer;
import org.xnap.plugin.Plugin;
import org.xnap.plugin.opennap.OpenNapPlugin;
import org.xnap.plugin.opennap.net.msg.ExceptionListener;
import org.xnap.plugin.opennap.net.msg.FilenameMessage;
import org.xnap.plugin.opennap.net.msg.MessageHandler;
import org.xnap.plugin.opennap.net.msg.MessageListener;
import org.xnap.plugin.opennap.net.msg.client.AltDownloadRequestMessage;
import org.xnap.plugin.opennap.net.msg.client.DownloadRequestMessage;
import org.xnap.plugin.opennap.net.msg.client.PrivateMessage;
import org.xnap.plugin.opennap.net.msg.server.AcceptFailedMessage;
import org.xnap.plugin.opennap.net.msg.server.DownloadAckMessage;
import org.xnap.plugin.opennap.net.msg.server.GetErrorMessage;
import org.xnap.plugin.opennap.net.msg.server.QueueLimitMessage;
import org.xnap.plugin.opennap.net.msg.server.ServerMessage;
import org.xnap.plugin.opennap.net.msg.server.UserSignoffMessage;
import org.xnap.plugin.opennap.net.msg.server.UserSignonMessage;
import org.xnap.transfer.AbstractTransfer;
import org.xnap.transfer.Download;
import org.xnap.transfer.Segment;
import org.xnap.transfer.action.AbstractRaisePriorityAction;
import org.xnap.transfer.action.AbstractStartAction;
import org.xnap.transfer.action.AbstractStopAction;
import org.xnap.transfer.action.AbstractTransferAction;
import org.xnap.util.FileHelper;
import org.xnap.util.FiniteStateMachine;
import org.xnap.util.IllegalOperationException;
import org.xnap.util.Scheduler;
import org.xnap.util.State;
import org.xnap.util.XNapTask;
/**
* Downloads a file.
*/
public class OpenNapDownload extends AbstractTransfer
implements Download, ExceptionListener, MessageListener, SocketListener,
StateListener {
//--- Constant(s) ---
/**
* Do not send //WantQueue more times than this. Once should be
* enough anyway. */
public static final int MAX_WANT_QUEUE = 2;
/**
* Requests are sent in this interval if this download could not
* be remotely queued. */
public static final int REQUEST_INTERVAL = 1 * 60 * 1000;
/**
* Requests are sent in REQUEST_INTERVAL * QUEUED_REQUEST_FACTOR
* if the download is remotely queued. */
public static final int QUEUED_REQUEST_FACTOR = 5;
/**
* If no reponse is received within MAX_RESPONSE_LAG milli seconds
* the peer is considered to be offline and the download is
* stopped.
*
* <p>This should be greater than REQUEST_INTERVAL *
* QUEUED_REQUEST_FACTOR. */
public static final int MAX_RESPONSE_LAG
= 2 * REQUEST_INTERVAL * QUEUED_REQUEST_FACTOR + 2 * 1000;
/**
* The maximum time to wait for a connect from the other peer in
* case of a firewalled download.
*/
public static final int WAITING_TIMEOUT = 2 * 60 * 1000;
/**
* Operation has been aborted.
*/
public static final State AQUIRING_SLOT
= new State(XNap.tr("Waiting for local download slot"));
/**
* The state transition table.
*
* <p>Once State.SUCCEEDED is entered the download was successful
* and the state will not change anymore.
*/
private static final Hashtable TRANSITION_TABLE;
static {
State[][] table = new State[][] {
{ State.NOT_STARTED, // the initial state
AQUIRING_SLOT, },
{ AQUIRING_SLOT, // aquires a slot from the local peer object
State.REQUESTING, State.STOPPING, },
{ State.REQUESTING, // sends requests by starting the requestor task
State.CONNECTING, State.FAILED, State.STOPPING, State.WAITING, },
{ State.WAITING, // in case peer is firewalled: waits for a connect
State.DOWNLOADING, State.FAILED, State.STOPPING, },
{ State.CONNECTING, // connects to peer by opening a socket
State.DOWNLOADING, State.FAILED, State.STOPPING, },
{ State.DOWNLOADING, // transfers the data
State.SUCCEEDED, State.FAILED, State.STOPPING, },
{ State.STOPPING, // user has aborted download
State.NOT_STARTED, State.FAILED, },
{ State.FAILED, // the download was stopped due to an error
// and maybe cleaned up
AQUIRING_SLOT, },
};
TRANSITION_TABLE = FiniteStateMachine.createStateTable(table);
}
//--- Data field(s) ---
private static Logger logger = Logger.getLogger(OpenNapDownload.class);
private OpenNapDownloadContainer parent;
private OpenNapSegment[] segments = new OpenNapSegment[0];
private OpenNapSegment currentSegment;
private OpenNapSearchResult result;
private StateMachine sm = new StateMachine();
private OpenNapDownloadRunner runner;
private OpenNapServer server;
private int queuePos = -1;
private DownloadSocket inSocket;
private AbstractTransferAction startAction = new StartAction();
private AbstractTransferAction stopAction = new StopAction();
private AbstractTransferAction requestAction = new RequestAction();
private long bytesTransferred = 0;
private long offset = 0;
/**
* The number of //WantQueue messages that have been sent.
*/
private int wantQueueCount = 0;
private int priority = 1;
/**
* The number of times we have waited for the peer to connect to us.
*/
private int waitingCount = 0;
/**
* The time of the answer of the last response to a download request.
*/
private long lastResponseReceived;
private long lastRequestSend;
//--- Constructor(s) ---
public OpenNapDownload(OpenNapDownloadContainer parent, OpenNapSearchResult result)
{
this.parent = parent;
this.result = result;
this.server = result.getOpenNapUser().getServer();
startAction.setEnabledLater(true);
stopAction.setEnabledLater(false);
// send whois query to update clientInfo, in case we need it later
result.getOpenNapUser().update();
}
//--- Method(s) ---
/**
* Tries to aquire a download slot. */
private void aquireDownloadSlot()
{
synchronized(getResult().getOpenNapUser()) {
if (!getResult().getOpenNapUser().isDownloadLimitReached()) {
try {
setState(State.REQUESTING);
}
catch (IllegalOperationException e) {
logger.warn("unexpected state", e);
}
}
}
}
/**
* Increased the number of transferred bytes.
*
* <p>Invoked by {@link OpenNapDownloadRunner#requestSegment()}.
*/
void commit(int bytes)
{
parent.commit(bytes);
bytesTransferred += bytes;
}
public long getBytesTransferred()
{
return bytesTransferred;
}
public Action[] getActions()
{
return new Action[] { startAction, stopAction, requestAction, };
}
public File getFile()
{
OpenNapSegment segment = getSegment();
return (segment != null) ? segment.getFile() : null;
}
public long getFilesize()
{
OpenNapSegment segment = getSegment();
return (segment != null) ? segment.getEnd() - offset : 0;
}
public Peer getPeer()
{
return getResult().getPeer();
}
public Plugin getPlugin()
{
return OpenNapPlugin.getInstance();
}
public OpenNapSearchResult getResult()
{
return result;
}
public OpenNapSegment getSegment()
{
return currentSegment;
}
public Segment[] getSegments()
{
return segments;
}
public String getStatus()
{
return sm.getDescription();
}
public long getTotalBytesTransferred()
{
return getBytesTransferred();
}
public boolean isDone()
{
State s = sm.getState();
return s == State.SUCCEEDED || s == State.FAILED;
}
public boolean isRestartable()
{
return sm.getState() != State.SUCCEEDED;
}
public boolean isRunning()
{
return sm.getState() == State.DOWNLOADING;
}
public boolean isQueued()
{
return (getQueuePosition() > 0);
}
public boolean socketReceived(IncomingSocket s)
{
if (s instanceof DownloadSocket) {
DownloadSocket d = (DownloadSocket)s;
if (getPeer().getName().equals(d.nick)
&& getResult().getFilename().equals(d.filename)) {
inSocket = d;
try {
setState(State.CONNECTING);
}
catch (IllegalOperationException e) {
logger.warn("unexpected state", e);
}
return true;
}
}
return false;
}
public int getQueuePosition()
{
return queuePos;
}
File createIncompleteFile() throws IOException
{
return FileHelper.createIncompleteFile(parent.getFilename());
}
OpenNapDownloadContainer getParent()
{
return parent;
}
OpenNapServer getServer()
{
return server;
}
/**
* Requests a segment from the parent {@link OpenNapDownloadContainer}
* and adds it to the list of segments if successful.
*
* <p>Invoked by {@link OpenNapDownloadRunner#requestSegment()}.
*
* @see #getSegments()
*/
OpenNapSegment requestSegment()
{
currentSegment = parent.getSegmentManager().requestSegment(this);
if (currentSegment != null) {
OpenNapSegment[] tmp = new OpenNapSegment[segments.length + 1];
System.arraycopy(segments, 0, tmp, 0, segments.length);
tmp[segments.length] = currentSegment;
segments = tmp;
bytesTransferred = 0;
offset = currentSegment.getStart()
+ currentSegment.getTransferred();
}
return currentSegment;
}
/**
* Clones segment and returns it to the parent
* {@link OpenNapDownloadContainer}.
*
* <p>Invoked by {@link OpenNapDownloadRunner}.
*/
void returnSegment(OpenNapSegment segment) throws IOException
{
// copy segment and trim down to transferred data
currentSegment = (OpenNapSegment)segment.clone();
segments[segments.length - 1] = currentSegment;
currentSegment.trim();
parent.getSegmentManager().returnSegment(segment);
}
void setState(State newState, String description)
{
sm.setState(newState, description);
stateChanged();
}
void setState(State newState)
{
sm.setState(newState);
stateChanged();
}
void setStateDescription(String description)
{
sm.setDescription(description);
stateChanged();
}
void setTotalMinimum(String description)
{
sm.setDescription(description);
stateChanged();
}
/**
* Invoked by {@link OpenNapDownloadContainer}.
*/
void start()
{
try {
setState(AQUIRING_SLOT);
}
catch (IllegalOperationException e) {
logger.warn("unexpected state", e);
}
}
void stop(String reason)
{
setQueuePosition(-1);
try {
setState(State.STOPPING, reason);
}
catch (IllegalOperationException e) {
// ignore exception, usually when this occurs we are
// usually already stopped and stop() is only invoked as a
// safe-guard
//logger.warn("unexpected state", e);
}
}
private void setQueuePosition(int queuePos)
{
this.queuePos = queuePos;
parent.remotelyQueued(queuePos);
}
/**
* Sends a download request.
*/
private void sendRequest()
{
DownloadRequestMessage ms = new DownloadRequestMessage
(getPeer().getName(), getResult().getFilename());
ms.setExceptionListener(this);
MessageHandler.send(getServer(), ms);
lastRequestSend = System.currentTimeMillis();
setStateDescription(XNap.tr("Requesting"));
}
/**
* Sends a '//WantQueue' message, if client is known to honor
* want queue requests.
*
* Returns true, if a download request should be sent.
*/
private boolean sendWantQueue()
{
if (wantQueueCount >= MAX_WANT_QUEUE) {
// avoid sending to many requests, some clients might not
// queue us at all or return 0 as a valid queue position
return false;
}
// some clients might obfuscate their client info so we might fail
// to recognize them as WinMX 2.6, therefore we send WantQueues
// to them too.
String client = getResult().getOpenNapUser().getClientInfo();
boolean send = false;
if (client != null) {
send |= client.startsWith("WinMX v2.6");
send |= client.startsWith("Napigator");
send |= client.startsWith("TrippyMX");
send |= client.startsWith("Utatane");
send |= wantQueueCount > 1 && client.startsWith("WinMX");
}
if (send) {
MessageHandler.send
(getServer(),
new PrivateMessage(getPeer().getName(), "//WantQueue"));
}
wantQueueCount++;
return true;
}
/**
* Invoked when the user download slot state changes.
*/
public void stateChanged(StateEvent e)
{
aquireDownloadSlot();
}
public void exceptionThrown(Exception e)
{
try {
setState(State.NOT_STARTED, e.getLocalizedMessage());
}
catch (IllegalOperationException e2) {
logger.warn("unexpected state", e2);
}
}
/**
* Handles messages received from the server as response to the download
* request. Notifies the parent download container if download is ready to
* start or if it should be removed due to an error message received from
* the server.
*/
public void messageReceived(ServerMessage m)
{
if (m instanceof FilenameMessage) {
FilenameMessage msg = (FilenameMessage)m;
if (m.getServer() == getServer()
&& msg.getFilename().equals(getResult().getFilename())
&& msg.getNick().equals(getResult().getPeer().getName())) {
// this msg is only for us
m.consume();
try {
handleMessage(m);
}
catch (IllegalOperationException e) {
logger.warn("unexpected state", e);
}
}
}
else if (m instanceof UserSignonMessage) {
// FIX: we should try again
}
else if (m instanceof UserSignoffMessage) {
if (((UserSignoffMessage)m).nick.equals
(getResult().getPeer().getName())) {
stop(XNap.tr("User is offline"));
}
}
}
/**
* @throws IllegalOperationException
*/
private void handleMessage(ServerMessage m)
{
if (m instanceof QueueLimitMessage) {
lastResponseReceived = System.currentTimeMillis();
int pos = ((QueueLimitMessage)m).maxDownloads;
if (pos == 0 || pos >= 10000) {
setQueuePosition(0);
if (sendWantQueue()) {
sendRequest();
}
}
else if (pos > 0 && pos < 10000) {
setQueuePosition(pos);
}
else { // pos < 0, should never happen
setQueuePosition(-1);
}
setStateDescription(XNap.tr("Remotely queued"));
logger.debug(getPeer().getName() + " has queued us at pos "
+ getQueuePosition());
}
else if (m instanceof AcceptFailedMessage
|| m instanceof GetErrorMessage) {
setState(State.FAILED, XNap.tr("Request was rejected"));
}
else if (m instanceof DownloadAckMessage) {
DownloadAckMessage ackMsg = (DownloadAckMessage)m;
getResult().getOpenNapUser().setHost(ackMsg.ip);
getResult().getOpenNapUser().setPort(ackMsg.port);
if (ackMsg.port != 0) {
setState(State.CONNECTING);
}
else if (getServer().getLocalPort() == 0) {
setState(State.FAILED, XNap.tr("Both parties are firewalled"));
}
else {
setState(State.WAITING, XNap.tr("Waiting for connect") + "...");
}
}
}
private void subscribe(MessageListener ms)
{
MessageHandler.subscribe(DownloadAckMessage.TYPE, ms);
MessageHandler.subscribe(QueueLimitMessage.TYPE, ms);
MessageHandler.subscribe(GetErrorMessage.TYPE, ms);
MessageHandler.subscribe(AcceptFailedMessage.TYPE, ms);
}
private void unsubscribe(MessageListener ms)
{
MessageHandler.unsubscribe(DownloadAckMessage.TYPE, ms);
MessageHandler.unsubscribe(QueueLimitMessage.TYPE, ms);
MessageHandler.unsubscribe(GetErrorMessage.TYPE, ms);
MessageHandler.unsubscribe(AcceptFailedMessage.TYPE, ms);
}
//--- Inner Class(es) ---
private class StateMachine extends FiniteStateMachine
{
//--- Data Field(s) ---
private Thread t;
private RequestorTask requestSender;
private WaitingTimeoutTask waitingTask;
//--- Constructor(s) ---
public StateMachine()
{
super(State.NOT_STARTED, TRANSITION_TABLE);
}
//--- Method(s) ---
protected synchronized void stateChanged(State oldState,
State newState)
{
if (oldState == AQUIRING_SLOT) {
getResult().getOpenNapUser().getParent().removeStateListener
(OpenNapDownload.this);
}
if (newState == AQUIRING_SLOT) {
MessageHandler.subscribe
(UserSignonMessage.TYPE, OpenNapDownload.this);
MessageHandler.subscribe
(UserSignoffMessage.TYPE, OpenNapDownload.this);
waitingCount = 0;
getResult().getOpenNapUser().getParent().addStateListener
(OpenNapDownload.this);
aquireDownloadSlot();
}
else if (newState == State.REQUESTING) {
if (!getParent().started(OpenNapDownload.this)) {
this.setState
(State.FAILED,
XNap.tr("Could not start download"));
return;
}
// initialize variables
queuePos = -1;
wantQueueCount = 0;
lastResponseReceived = System.currentTimeMillis();
subscribe(OpenNapDownload.this);
requestSender = new RequestorTask();
Scheduler.run(0, REQUEST_INTERVAL, requestSender);
startAction.setEnabledLater(false);
stopAction.setEnabledLater(true);
requestAction.setEnabledLater(true);
}
else if (newState == State.WAITING) {
getServer().getListener().addSocketListener
(OpenNapDownload.this);
AltDownloadRequestMessage msg
= new AltDownloadRequestMessage
(getPeer().getName(), getResult().getFilename());
MessageHandler.send(getServer(), msg);
waitingCount++;
waitingTask = new WaitingTimeoutTask();
Scheduler.run(WAITING_TIMEOUT, waitingTask);
}
else if (newState == State.CONNECTING) {
runner = new OpenNapDownloadRunner
(OpenNapDownload.this, inSocket);
t = new Thread(runner, "OpenNapDownload");
t.start();
}
else if (newState == State.DOWNLOADING) {
transferStarted();
parent.transferStarted();
}
else if (newState == State.STOPPING) {
if (runner != null) {
// the runner will transfer the state to NOT_STARTED
runner.stop();
t.interrupt();
}
else {
this.setState(State.NOT_STARTED, getDescription());
}
}
else if (newState == State.NOT_STARTED
|| newState == State.FAILED) {
startAction.setEnabledLater(true);
}
if (newState == State.STOPPING
|| newState == State.FAILED) {
// free the aquired local download slot
getResult().getOpenNapUser().getParent().downloadStopped();
}
if (newState == State.NOT_STARTED
|| newState == State.FAILED
|| newState == State.SUCCEEDED) {
// the download is done
runner = null;
t = null;
MessageHandler.unsubscribe
(UserSignonMessage.TYPE, OpenNapDownload.this);
MessageHandler.unsubscribe
(UserSignoffMessage.TYPE, OpenNapDownload.this);
stopAction.setEnabledLater(false);
getParent().stopped(OpenNapDownload.this);
setQueuePosition(-1);
}
if (oldState == State.REQUESTING) {
requestAction.setEnabledLater(false);
// the state could have changed to FAILED right away
// and the requestSender might not have been started
if (requestSender != null) {
requestSender.cancel();
requestSender = null;
unsubscribe(OpenNapDownload.this);
}
}
else if (oldState == State.WAITING) {
getServer().getListener().removeSocketListener
(OpenNapDownload.this);
if (waitingTask != null) {
waitingTask.cancel();
waitingTask = null;
}
}
else if (oldState == State.DOWNLOADING) {
transferStopped();
parent.transferStopped();
}
}
}
/**
* Resends the download request in regular intervals. */
private class RequestorTask extends XNapTask {
int skippedRequests = 0;
public void run()
{
if (lastResponseReceived + MAX_RESPONSE_LAG
< System.currentTimeMillis()) {
stop(XNap.tr("Request timed out."));
}
if (getQueuePosition() >= 0) {
String client = getResult().getOpenNapUser().getClientInfo();
if (client != null
&& client.startsWith("nap")) {
// nap does not support real queueing, it will
// never notify us that the slot was free
sendRequest();
}
else {
// only send request every nth time once we are queued
// to avoid superfluous server load
if (skippedRequests == QUEUED_REQUEST_FACTOR - 1) {
skippedRequests = 0;
sendRequest();
}
else {
skippedRequests++;
}
}
}
else {
sendRequest();
}
}
}
/**
* Resends the download request in regular intervals. */
private class WaitingTimeoutTask extends XNapTask {
public void run()
{
if (waitingCount < 3) {
setState(State.REQUESTING,
XNap.tr("Rerequesting, wait for connect timed out"));
}
else {
setState(State.FAILED,
XNap.tr("Giving up, wait timed out 3 times"));
}
}
}
private class RaisePriorityAction extends AbstractRaisePriorityAction {
public void actionPerformed(ActionEvent e)
{
priority += 1;
}
}
private class StartAction extends AbstractStartAction {
public void actionPerformed(ActionEvent e)
{
start();
}
}
private class StopAction extends AbstractStopAction {
public void actionPerformed(ActionEvent e)
{
stop(XNap.tr("Download stopped"));
}
}
private class RequestAction extends AbstractTransferAction {
public RequestAction()
{
putValue(Action.NAME, XNap.tr("Send Request"));
putValue(Action.SHORT_DESCRIPTION,
XNap.tr("Requeries the selected transfers."));
putValue(IconHelper.XNAP_ICON, "reload.png");
setEnabled(false);
}
public void actionPerformed(ActionEvent e)
{
sendRequest();
}
}
}
The table below shows all metrics for OpenNapDownload.java.



