Package xnap.plugin.nap.net

Source Code of xnap.plugin.nap.net.Download

/*
*  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.ThrottledInputStream;
import xnap.net.*;
import xnap.plugin.nap.Plugin;
import xnap.plugin.nap.net.msg.*;
import xnap.plugin.nap.net.msg.client.*;
import xnap.plugin.nap.net.msg.server.AcceptFailedMessage;
import xnap.plugin.nap.net.msg.server.DownloadAckMessage;
import xnap.plugin.nap.net.msg.server.ErrorMessage;
import xnap.plugin.nap.net.msg.server.GetErrorMessage;
import xnap.plugin.nap.net.msg.server.MessageListener;
import xnap.plugin.nap.net.msg.server.MessageStream;
import xnap.plugin.nap.net.msg.server.QueueLimitMessage;
import xnap.plugin.nap.net.msg.server.ServerMessage;
import xnap.plugin.nap.util.NapPreferences;

import xnap.util.*;

import java.io.*;
import java.net.*;
import java.util.*;
import org.apache.log4j.Logger;

public class Download extends AbstractDownload
    implements MessageListener, ExceptionListener
{

    // --- Constant(s) ---

    /**
     * Server needs to send download ack.
     */
    public static final int SERVER_TIMEOUT = 2 * 60 * 1000;

    /**
     * Socket timeout during connect.
     */
    public static final int CONNECT_TIMEOUT = 1 * 60 * 1000;

    /**
     * Requests are sent in this interval if this download is remotely queued.
     */
    public static final int QUEUED_REQUEST_INTERVAL = 5 * 60 * 1000;

    /**
     * Requests are sent in this interval if this download cound not be
     * remotely queued.
     */
    public static final int REQUEST_INTERVAL = 2 * 60 * 1000;

    /**
     * Do not send //WantQueue more times than this. Once should be enough
     * anyway.
     */
    public static final int MAX_WANT_QUEUE = 2;
   
    // --- Data Field(s) ---
   
    protected static Logger logger = Logger.getLogger(Download.class);

    private SearchResult sr;
    private Server server;

    protected Socket socket;
    protected InputStream in;
    protected OutputStream out;

    protected long offset;
    protected int port;
    protected String ip;

    private boolean subscribed = false;
    private boolean requestSent = false;
    private long lastRequestSent = 0;

    private int wantQueueCount = 0;

    // --- Constructor(s) ---
   
    public Download(SearchResult sr)
    {
  super(sr.getUser(), sr.getFilesize());

  this.sr = sr;
  this.server = sr.getNapUser().getServer();

  // send whois query to update clientInfo, in case we need it later
  sr.getNapUser().update();
    }

    // --- Method(s) ---

    public int available() throws IOException
    {
  return in.available();
    }

    public void close(boolean sendMsg)
    {
  if (sendMsg) {
      MessageHandler.send(new DownloadCompleteMessage());
  }

        try {
      if (socket != null)
    socket.close();
      if (in != null)
    in.close();
      if (out != null)
    out.close();
        }
  catch (IOException e) {
        }
    }

    public void close()
    {
  close(true);
    }

    public boolean connect(long offset) throws IOException
    {
  if (requestSent) {
      requestSent = false;
      sr.getNapUser().incQueuedCount(-1);
  }

  // we are not queued anymore
  setQueuePos(-1);
  wantQueueCount = 0;

  start();
  this.offset = offset;


  if (port == 0) {
      if (!establishReverseStream()) {
    return false;
      }
  }
  else {
      establishStream(ip, port);
  }

  in = new ThrottledInputStream(in);
  MessageHandler.send(new DownloadingFileMessage());

  return true;
    }

    public void dequeue()
    {
  if (requestSent) {
      requestSent = false;
      sr.getNapUser().incQueuedCount(-1);
  }

  wantQueueCount = 0;
  lastRequestSent = 0;
  unsubscribe(this);
    }

    public void exceptionThrown(Exception e)
    {
  getParent().remove(this);
    }

    public void enqueue(IDownloadContainer parent)
    {
  setParent(parent);
  subscribe(this);

  boolean allowed = sr.getNapUser().isAllowedToRequestDownload();
  if (allowed || requestSent) {
      if (!requestSent) {
    // after we have sent the request, we might not be allowed
    // anymore because the queue count was increased therefore
    // requestSent is set to true
    requestSent = true;
    sr.getNapUser().incQueuedCount(1);
      }
      sendDownloadRequest(false);
  }
  else if (!allowed && requestSent) {
      // we are not allowed anymore
      requestSent = false;
      sr.getNapUser().incQueuedCount(-1);
  }
 
  lastTry = System.currentTimeMillis();
    }

    private int getRequestInterval()
    {
  // nap does not support remote queueing
  String client = sr.getUser().getClientInfo();
  if ((client != null && client.startsWith("nap"))
      || getQueuePos() <= 0) {
      return REQUEST_INTERVAL;
  }
  else {
      return QUEUED_REQUEST_INTERVAL;
  }
    }

    private void sendDownloadRequest(boolean force)
    {
  if (force || (System.currentTimeMillis() - lastRequestSent
          > getRequestInterval())) {
      DownloadRequestMessage ms = new DownloadRequestMessage
    (sr.getUser().getName(), sr.getFilename());
      ms.setExceptionListener(this);
      MessageHandler.send(server, ms);
      lastRequestSent = System.currentTimeMillis();
  }
    }

    /**
     * Opens socket and requests file.
     */
    private void establishStream(String ip, int port) throws IOException
    {
  logger.debug("opening socket " + ip + ":" + port);

  //socket = new Socket(ip, port);
  socket = NetHelper.connect(ip, port, CONNECT_TIMEOUT);
  try {
      socket.setSoTimeout(CONNECT_TIMEOUT);
  }
  catch (SocketException s) {
  }

  out = socket.getOutputStream();
  in = new BufferedInputStream(socket.getInputStream());
 
  // read magic number '1'
  logger.debug("reading magic number");
  char c = (char)in.read();
  if (c != '1') {
      throw new IOException(Plugin.tr("Invalid request"));
  }
 
  // get request needs to be split over 2 packets
  String message = "GET";
 
  out.write(message.getBytes());
  out.flush();
 
  message = server.getUsername() + " "
      + "\"" + sr.getFilename() + "\"" + " " + offset;
 
  logger.debug("the whole request: " + message);

  out.write(message.getBytes());
  out.flush();
 
  String expected = sr.getFilesize() + "";
  StringBuffer sb = new StringBuffer();
  while (sb.length() < expected.length()) {
      int b = in.read();
     
      if (b == -1) {
    throw new IOException(Plugin.tr("Socket error"));
      }

      c = (char)b;
      if (Character.isDigit(c)) {
    // ignore leading zeros
    if (!(sr.getFilesize() != 0 && sb.length() == 0 && c == '0')) {
        sb.append(c);
    }
      }
      else if (c == 'F') {
    throw new IOException(Plugin.tr("File not shared"));
      }
      else {
    throw new IOException(Plugin.tr("Invalid request"));
      }
  }

  logger.debug("file length: " + sb.toString());
 
  if (Long.parseLong(sb.toString()) != sr.getFilesize()) {
      throw new IOException(Plugin.tr("Filesizes did not match"));
  }
    }

    /**
     * Waits for push.
     */
    private boolean establishReverseStream() throws IOException
    {
  if (server.getLocalPort() == 0) {
      throw new IOException(Plugin.tr("Both parties firewalled"));
  }

        MessageStream ms = new MessageStream(server);
  MessageHandler.subscribe(ErrorMessage.TYPE, ms);

        server.send(new AltDownloadRequestMessage(sr.getUser().getName(),
              sr.getFilename()));

  DownloadSocket ds = new DownloadSocket
      (sr.getUser().getName(), sr.getFilename(), sr.getFilesize());

  checkForError(ms);

  if (getQueuePos() > 0) {
      return false;
  }

  // FIX: we might get queued in the mean time
  ds = (DownloadSocket)server.getListener().waitForSocket
      (ds, CONNECT_TIMEOUT);
 
  checkForError(ms);
  MessageHandler.unsubscribe(ErrorMessage.TYPE, ms);

  if (ds == null) {
      if (getQueuePos() >= 0) {
    return false;
      }
      else {
    throw new IOException(Plugin.tr("Listener timeout"));
      }
  }

  logger.debug("reverse stream socket established");

  socket = ds.socket;
  try {
      socket.setSoTimeout(CONNECT_TIMEOUT);
  }
  catch (SocketException e) {
  }

  out = socket.getOutputStream();
  in = ds.in;

  // write offset
  out.write((new Long(offset)).toString().getBytes());
  out.flush();

  return true;
    }

    private void checkForError(MessageStream ms) throws IOException
    {
  while (ms.hasNext()) {
      ServerMessage m = (ServerMessage)ms.next();

      if (m instanceof ErrorMessage) {
    String expect = sr.getUser().getName() + " is not firewalled";
    if (((ErrorMessage)m).message.equals(expect)) {
        MessageHandler.unsubscribe(ErrorMessage.TYPE, ms);
        throw new IOException(Plugin.tr("User is not firewalled"));
    }
      }
  }
    }

    /**
     * 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)m).getFilename().equals(sr.getFilename())
      && m.getServer() == server) {
      // this msg is only for us
      m.consume();
  }
  else {
      // not for us
      return;
  }
  
  // try to find out, what kind of message we have received
  if (m instanceof QueueLimitMessage) {
      int pos = ((QueueLimitMessage)m).maxDownloads;
      if (pos == 0 || pos >= 10000) {
    setQueuePos(0);
    if (sendWantQueue()) {
        sendDownloadRequest(true);
    }
      }
      else if (pos > 0 && pos < 10000) {
    setQueuePos(pos);
      }
      else {
    setQueuePos(-1);
      }

      logger.debug(sr.getUser().getName() + " has queued us at pos "
       + getQueuePos());
  }
  else if (m instanceof AcceptFailedMessage
     || m instanceof GetErrorMessage) {
      logger.debug(sr.getUser().getName()
       + " telling parent to remove " + sr.getFilename());
      getParent().remove(this);
  }
  else if (m instanceof DownloadAckMessage) {
      DownloadAckMessage dam = (DownloadAckMessage)m;
     
      ip = dam.ip;
      port = dam.port;
      logger.debug("telling parent to start me " + ip + ":" + port + " "
       + this);
      getParent().start(this);
  }
    }

    public int read(byte[] b, int offset, int length) throws IOException
    {
  return in.read(b, offset, length);
    }

    /**
     * Returns true, if a download request should be sent.
     */
    public 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 thier client info so we might fail
  // to recognize them as WinMX 2.6, therefore we send WantQueues
  // to them
  String client = sr.getUser().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) {
      logger.debug("Sending //WantQueue to " + sr.getUser().getName()
       + " who uses client " + client);
        MessageHandler.send(server, new PrivateMessage
      (sr.getUser().getName(), "//WantQueue"));
  }
  wantQueueCount++;

  return true;
    }

    /**
     * Subscribes to all messages relevant for downloading.
     */

    private void subscribe(MessageListener ms)
    {
  if (subscribed) {
      return;
  }

      MessageHandler.subscribe(DownloadAckMessage.TYPE, ms);
        MessageHandler.subscribe(QueueLimitMessage.TYPE, ms);
        MessageHandler.subscribe(GetErrorMessage.TYPE, ms);
        MessageHandler.subscribe(AcceptFailedMessage.TYPE, ms);

  subscribed = true;
    }

    /**
     * Unsubscribes from all messages relevant for downloading.
     */

    private void unsubscribe(MessageListener ms)
    {
  if (!subscribed) {
      return;
  }

  MessageHandler.unsubscribe(DownloadAckMessage.TYPE, ms);
        MessageHandler.unsubscribe(QueueLimitMessage.TYPE, ms);
        MessageHandler.unsubscribe(GetErrorMessage.TYPE, ms);
        MessageHandler.unsubscribe(AcceptFailedMessage.TYPE, ms);

  subscribed = false;
    }

    public String toString()
    {
  return "download " + sr.getFilename()  + "@" + sr.getUser().getName();
    }
}
TOP

Related Classes of xnap.plugin.nap.net.Download

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.