/*
* XNap
*
* A pure java file sharing client.
*
* See 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; 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
*
*/
package xnap.plugin.nap.net;
import xnap.io.*;
import xnap.net.*;
import xnap.plugin.nap.Plugin;
import xnap.plugin.nap.net.msg.MessageHandler;
import xnap.plugin.nap.net.msg.client.AcceptFailedMessage;
import xnap.plugin.nap.net.msg.client.PrivateMessage;
import xnap.plugin.nap.net.msg.client.UploadAckMessage;
import xnap.plugin.nap.net.msg.client.UploadCompleteMessage;
import xnap.plugin.nap.net.msg.client.UploadingFileMessage;
import xnap.plugin.nap.net.msg.server.AltDownloadAckMessage;
import xnap.plugin.nap.net.msg.server.MessageListener;
import xnap.plugin.nap.net.msg.server.ServerMessage;
import xnap.plugin.nap.util.NapFileHelper;
import xnap.plugin.nap.util.NapPreferences;
import xnap.util.*;
import java.io.*;
import java.net.*;
import java.util.*;
/**
* Handles both normal and firewalled uploads.
*/
public class Upload extends AbstractUpload implements MessageListener
{
//--- Constant(s) ---
/**
* Socket timeout during connect.
*/
public static final int CONNECT_TIMEOUT = 1 * 60 * 1000;
//--- Data field(s) ---
protected Server server;
protected Socket socket;
protected InputStream in;
protected OutputStream out;
protected String requestFilename;
protected String host;
protected int port;
private Object lock = new Object();
//--- Constructor(s) ---
public Upload(String nick, File file, Server server, String filename)
{
super(server.getUser(nick));
this.file = file;
this.server = server;
this.requestFilename = filename;
}
//--- Method(s) ---
/**
* Sends <code>UploadAckMessage</code> to server and waits CONNECT_TIMEOUT
* secs for an upload socket.
*/
public void connect() throws IOException
{
if (!server.isConnected()) {
throw new IOException(Plugin.tr("Server disconnected"));
}
if (!server.getUser(getUser().getName()).isAllowedToDownload()) {
throw new IOException(Plugin.tr("Leecher rejected"));
}
MessageHandler.subscribe(AltDownloadAckMessage.TYPE, this);
MessageHandler.send
(server, new UploadAckMessage(user.getName(), requestFilename));
UploadSocket us = null;
/* if user is not firewalled wait for a socket, this does not impede
the peer from sending an alternate dl request anyway. */
if (server.getLocalPort() != 0) {
us = new UploadSocket(getUser().getName(), requestFilename);
us = (UploadSocket)server.getListener().waitForSocket
(us, CONNECT_TIMEOUT);
}
else {
synchronized(lock) {
try {
lock.wait(CONNECT_TIMEOUT);
}
catch (InterruptedException e) {
}
}
}
MessageHandler.unsubscribe(AltDownloadAckMessage.TYPE, this);
/* while we wait for an upload socket an AltDownloadAckMessage could
come in to signal a firewalled upload, we check for both. */
if (us == null && host == null && port == 0) {
throw new IOException(Plugin.tr("Listener timeout"));
}
// check again in case whois info wasn't ready yet
if (!server.getUser(getUser().getName()).isAllowedToDownload()) {
if (us != null) {
if (us.socket != null) {
us.socket.close();
}
}
throw new IOException(Plugin.tr("Leecher rejected"));
}
if (us != null) {
socket = us.socket;
try {
socket.setSoTimeout(CONNECT_TIMEOUT);
}
catch (SocketException s) {
}
offset = us.offset;
try {
out = new ThrottledOutputStream(socket.getOutputStream());
establishStream();
}
catch (IOException ie) {
close(false);
throw(ie);
}
}
else {
try {
//socket = new Socket(host, port);
socket = NetHelper.connect(host, port, CONNECT_TIMEOUT);
try {
socket.setSoTimeout(CONNECT_TIMEOUT);
}
catch (SocketException s) {
}
in = new BufferedInputStream(socket.getInputStream());
out = new ThrottledOutputStream(socket.getOutputStream());
establishReverseStream();
}
catch (IOException ie) {
close(false);
throw(ie);
}
}
MessageHandler.send(new UploadingFileMessage());
}
public void close(boolean sendMsg)
{
if (sendMsg) {
MessageHandler.send(new UploadCompleteMessage());
}
try {
if (in != null)
in.close();
if (out != null)
out.close();
if (socket != null)
socket.close();
}
catch (IOException e) {
}
}
public void close()
{
close(true);
}
/**
* Changed implementation: <code>equals</code> is called before adding a
* new upload to the {@link UploadQueue} to check if it's not already
* there. */
public boolean equals(Object o)
{
if (o instanceof Upload) {
Upload u = (Upload)o;
// we don't check the server yet
return (file.equals(u.getFile()) && user.equals(u.getUser()));
// Socket s = u.getSocket();
// return ((s != null
// && getSocket().getInetAddress().equals(s.getInetAddress())
// && getSocket().getPort() == s.getPort()));
}
return false;
}
/**
* Needed for equals.
*/
public Socket getSocket()
{
return socket;
}
public void reject()
{
MessageHandler.send(server,
new AcceptFailedMessage(getUser().getName(),
requestFilename));
}
public void write(byte[] b, int len) throws IOException
{
out.write(b, 0, len);
}
/**
* Most of the <code>establishStream()</code> is in the {@link
* UploadSocket} class which is not that beautiful. This problem should be
* tackled. */
protected void establishStream() throws IOException
{
if (file == null) {
String response = "INVALID REQUEST";
out.write(response.getBytes());
throw new IOException(Plugin.tr("Invalid request"));
}
out.write((new Long(file.length())).toString().getBytes());
}
/**
* Makes {@link AltUpload} futile. This is necessary since the decision
* whether it is a normal upload or a firewalled upload is done rather
* late. */
protected void establishReverseStream() throws IOException
{
String message;
// read magic number '1'
char c = (char)in.read();
if (c != '1') {
throw new IOException(Plugin.tr("Invalid request"));
}
message = "SEND";
out.write(message.getBytes());
out.flush();
message = server.getUsername() + " \"" + requestFilename
+ "\" " + file.length();
Debug.log("nap ul: -> " + message);
out.write(message.getBytes());
out.flush();
byte data[] = new byte[1000];
int j = in.read(data);
if (j <= 0) {
throw new IOException(Plugin.tr("Socket error"));
}
String response = new String(data, 0, j);
Debug.log("nap ul: <- " + response);
try {
offset = Long.parseLong(response);
}
catch (NumberFormatException e) {
Debug.log(e);
throw new IOException(Plugin.tr("Invalid request"));
}
Debug.log("nap ul: offset <- " + response);
}
public void messageReceived(ServerMessage msg)
{
if (msg instanceof AltDownloadAckMessage) {
AltDownloadAckMessage m = (AltDownloadAckMessage)msg;
if (m.nick.equals(user.getName())
&& m.filename.equals(requestFilename)) {
m.consume();
host = m.ip;
port = m.port;
// wake up thread waiting in connect()
synchronized (lock) {
lock.notify();
}
}
}
}
}