Yaaic/app/src/main/java/org/jibble/pircbot/DccFileTransfer.java

506 lines
16 KiB
Java

/*
Copyright Paul James Mutton, 2001-2007, http://www.jibble.org/
This file is part of PircBot.
This software is dual-licensed, allowing you to choose between the GNU
General Public License (GPL) and the www.jibble.org Commercial License.
Since the GPL may be too restrictive for use in a proprietary application,
a commercial license is also provided. Full license information can be
found at http://www.jibble.org/licenses/
*/
package org.jibble.pircbot;
import java.net.*;
import java.io.*;
/**
* This class is used to administer a DCC file transfer.
*
* @since 1.2.0
* @author Paul James Mutton,
* <a href="http://www.jibble.org/">http://www.jibble.org/</a>
* @version 1.4.6 (Build time: Wed Apr 11 19:20:59 2007)
*/
public class DccFileTransfer {
/**
* The default buffer size to use when sending and receiving files.
*/
public static final int BUFFER_SIZE = 1024;
/**
* Constructor used for receiving files.
*/
DccFileTransfer(PircBot bot, DccManager manager, String nick, String login, String hostname, String type, String filename, long address, int port, long size) {
_bot = bot;
_manager = manager;
_nick = nick;
_login = login;
_hostname = hostname;
_type = type;
_file = new File(filename);
_address = address;
_port = port;
_size = size;
_received = false;
_incoming = true;
}
/**
* Constructor used for sending files.
*/
DccFileTransfer(PircBot bot, DccManager manager, File file, String nick, int timeout) {
_bot = bot;
_manager = manager;
_nick = nick;
_file = file;
_size = file.length();
_timeout = timeout;
_received = true;
_incoming = false;
}
/**
* Receives a DccFileTransfer and writes it to the specified file.
* Resuming allows a partial download to be continue from the end of
* the current file contents.
*
* @param file The file to write to.
* @param resume True if you wish to try and resume the download instead
* of overwriting an existing file.
*
*/
public synchronized void receive(File file, boolean resume) {
if (!_received) {
_received = true;
_file = file;
if (_type.equals("SEND") && resume) {
_progress = file.length();
if (_progress == 0) {
doReceive(file, false);
}
else {
_bot.sendCTCPCommand(_nick, "DCC RESUME file.ext " + _port + " " + _progress);
_manager.addAwaitingResume(this);
}
}
else {
_progress = file.length();
doReceive(file, resume);
}
}
}
/**
* Receive the file in a new thread.
*/
void doReceive(final File file, final boolean resume) {
new Thread() {
public void run() {
BufferedOutputStream foutput = null;
Exception exception = null;
try {
// Convert the integer address to a proper IP address.
int[] ip = _bot.longToIp(_address);
String ipStr = ip[0] + "." + ip[1] + "." + ip[2] + "." + ip[3];
// Connect the socket and set a timeout.
_socket = new Socket(ipStr, _port);
_socket.setSoTimeout(30*1000);
_startTime = System.currentTimeMillis();
// No longer possible to resume this transfer once it's underway.
_manager.removeAwaitingResume(DccFileTransfer.this);
BufferedInputStream input = new BufferedInputStream(_socket.getInputStream());
BufferedOutputStream output = new BufferedOutputStream(_socket.getOutputStream());
// Following line fixed for jdk 1.1 compatibility.
foutput = new BufferedOutputStream(new FileOutputStream(file.getCanonicalPath(), resume));
byte[] inBuffer = new byte[BUFFER_SIZE];
byte[] outBuffer = new byte[4];
int bytesRead = 0;
while ((bytesRead = input.read(inBuffer, 0, inBuffer.length)) != -1) {
foutput.write(inBuffer, 0, bytesRead);
_progress += bytesRead;
// Send back an acknowledgement of how many bytes we have got so far.
outBuffer[0] = (byte) ((_progress >> 24) & 0xff);
outBuffer[1] = (byte) ((_progress >> 16) & 0xff);
outBuffer[2] = (byte) ((_progress >> 8) & 0xff);
outBuffer[3] = (byte) ((_progress >> 0) & 0xff);
output.write(outBuffer);
output.flush();
delay();
}
foutput.flush();
}
catch (Exception e) {
exception = e;
}
finally {
try {
foutput.close();
_socket.close();
}
catch (Exception anye) {
// Do nothing.
}
}
_bot.onFileTransferFinished(DccFileTransfer.this, exception);
}
}.start();
}
/**
* Method to send the file inside a new thread.
*/
void doSend(final boolean allowResume) {
new Thread() {
public void run() {
BufferedInputStream finput = null;
Exception exception = null;
try {
ServerSocket ss = null;
int[] ports = _bot.getDccPorts();
if (ports == null) {
// Use any free port.
ss = new ServerSocket(0);
}
else {
for (int i = 0; i < ports.length; i++) {
try {
ss = new ServerSocket(ports[i]);
// Found a port number we could use.
break;
}
catch (Exception e) {
// Do nothing; go round and try another port.
}
}
if (ss == null) {
// No ports could be used.
throw new IOException("All ports returned by getDccPorts() are in use.");
}
}
ss.setSoTimeout(_timeout);
_port = ss.getLocalPort();
InetAddress inetAddress = _bot.getDccInetAddress();
if (inetAddress == null) {
inetAddress = _bot.getInetAddress();
}
byte[] ip = inetAddress.getAddress();
long ipNum = _bot.ipToLong(ip);
// Rename the filename so it has no whitespace in it when we send it.
// .... I really should do this a bit more nicely at some point ....
String safeFilename = _file.getName().replace(' ', '_');
safeFilename = safeFilename.replace('\t', '_');
if (allowResume) {
_manager.addAwaitingResume(DccFileTransfer.this);
}
// Send the message to the user, telling them where to connect to in order to get the file.
_bot.sendCTCPCommand(_nick, "DCC SEND " + safeFilename + " " + ipNum + " " + _port + " " + _file.length());
// The client may now connect to us and download the file.
_socket = ss.accept();
_socket.setSoTimeout(30000);
_startTime = System.currentTimeMillis();
// No longer possible to resume this transfer once it's underway.
if (allowResume) {
_manager.removeAwaitingResume(DccFileTransfer.this);
}
// Might as well close the server socket now; it's finished with.
ss.close();
BufferedOutputStream output = new BufferedOutputStream(_socket.getOutputStream());
BufferedInputStream input = new BufferedInputStream(_socket.getInputStream());
finput = new BufferedInputStream(new FileInputStream(_file));
// Check for resuming.
if (_progress > 0) {
long bytesSkipped = 0;
while (bytesSkipped < _progress) {
bytesSkipped += finput.skip(_progress - bytesSkipped);
}
}
byte[] outBuffer = new byte[BUFFER_SIZE];
byte[] inBuffer = new byte[4];
int bytesRead = 0;
while ((bytesRead = finput.read(outBuffer, 0, outBuffer.length)) != -1) {
output.write(outBuffer, 0, bytesRead);
output.flush();
input.read(inBuffer, 0, inBuffer.length);
_progress += bytesRead;
delay();
}
}
catch (Exception e) {
exception = e;
}
finally {
try {
finput.close();
_socket.close();
}
catch (Exception e) {
// Do nothing.
}
}
_bot.onFileTransferFinished(DccFileTransfer.this, exception);
}
}.start();
}
/**
* Package mutator for setting the progress of the file transfer.
*/
void setProgress(long progress) {
_progress = progress;
}
/**
* Delay between packets.
*/
private void delay() {
if (_packetDelay > 0) {
try {
Thread.sleep(_packetDelay);
}
catch (InterruptedException e) {
// Do nothing.
}
}
}
/**
* Returns the nick of the other user taking part in this file transfer.
*
* @return the nick of the other user.
*
*/
public String getNick() {
return _nick;
}
/**
* Returns the login of the file sender.
*
* @return the login of the file sender. null if we are sending.
*
*/
public String getLogin() {
return _login;
}
/**
* Returns the hostname of the file sender.
*
* @return the hostname of the file sender. null if we are sending.
*
*/
public String getHostname() {
return _hostname;
}
/**
* Returns the suggested file to be used for this transfer.
*
* @return the suggested file to be used.
*
*/
public File getFile() {
return _file;
}
/**
* Returns the port number to be used when making the connection.
*
* @return the port number.
*
*/
public int getPort() {
return _port;
}
/**
* Returns true if the file transfer is incoming (somebody is sending
* the file to us).
*
* @return true if the file transfer is incoming.
*
*/
public boolean isIncoming() {
return _incoming;
}
/**
* Returns true if the file transfer is outgoing (we are sending the
* file to someone).
*
* @return true if the file transfer is outgoing.
*
*/
public boolean isOutgoing() {
return !isIncoming();
}
/**
* Sets the delay time between sending or receiving each packet.
* Default is 0.
* This is useful for throttling the speed of file transfers to maintain
* a good quality of service for other things on the machine or network.
*
* @param millis The number of milliseconds to wait between packets.
*
*/
public void setPacketDelay(long millis) {
_packetDelay = millis;
}
/**
* returns the delay time between each packet that is send or received.
*
* @return the delay between each packet.
*
*/
public long getPacketDelay() {
return _packetDelay;
}
/**
* Returns the size (in bytes) of the file being transfered.
*
* @return the size of the file. Returns -1 if the sender did not
* specify this value.
*/
public long getSize() {
return _size;
}
/**
* Returns the progress (in bytes) of the current file transfer.
* When resuming, this represents the total number of bytes in the
* file, which may be greater than the amount of bytes resumed in
* just this transfer.
*
* @return the progress of the transfer.
*/
public long getProgress() {
return _progress;
}
/**
* Returns the progress of the file transfer as a percentage.
* Note that this should never be negative, but could become
* greater than 100% if you attempt to resume a larger file
* onto a partially downloaded file that was smaller.
*
* @return the progress of the transfer as a percentage.
*/
public double getProgressPercentage() {
return 100 * (getProgress() / (double) getSize());
}
/**
* Stops the DCC file transfer by closing the connection.
*/
public void close() {
try {
_socket.close();
}
catch (Exception e) {
// Let the DCC manager worry about anything that may go wrong.
}
}
/**
* Returns the rate of data transfer in bytes per second.
* This value is an estimate based on the number of bytes
* transfered since the connection was established.
*
* @return data transfer rate in bytes per second.
*/
public long getTransferRate() {
long time = (System.currentTimeMillis() - _startTime) / 1000;
if (time <= 0) {
return 0;
}
return getProgress() / time;
}
/**
* Returns the address of the sender as a long.
*
* @return the address of the sender as a long.
*/
public long getNumericalAddress() {
return _address;
}
private PircBot _bot;
private DccManager _manager;
private String _nick;
private String _login = null;
private String _hostname = null;
private String _type;
private long _address;
private int _port;
private long _size;
private boolean _received;
private Socket _socket = null;
private long _progress = 0;
private File _file = null;
private int _timeout = 0;
private boolean _incoming;
private long _packetDelay = 0;
private long _startTime = 0;
}