/*
* DownloadHandler.java
*
* Copyright (C) 2008 AppleGrew
*
* 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 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 org.elite.jdcbot.framework;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.zip.InflaterInputStream;
import org.apache.tools.bzip2.CBZip2InputStream;
import org.slf4j.Logger;
/**
* Created on 26-May-08<br>
* Handels all the downloads from a single user for a session.
* <p>
* This is used internally, you are not required do anything with this.
* <p>
* This class is thread safe.
*
* @author AppleGrew
* @since 0.7
* @version 0.1.3
*
*/
public class DownloadHandler extends DCIO implements Runnable {
private static final Logger logger = GlobalObjects.getLogger(DownloadHandler.class);
private final int in_buffer_size = 64 * 1024; //64 KMB
private final int checkInterval = 100000; //This has been defined so polling frequency of CancelEntityQ is minimized.
private List<DUEntity> DownloadEntityQ;
private List<DUEntity> CancelEntityQ;
private User _u;
private jDCBot _jdcbot;
private DownloadManager _dm;
private Socket _socket = null;
private volatile boolean close = false;
private volatile boolean threadstarted = false;
private volatile boolean connectionFailed = false;
private Thread th;
public DownloadHandler(User user, jDCBot jdcbot, DownloadManager dm) {
_u = user;
_jdcbot = jdcbot;
_dm = dm;
th = null;
DownloadEntityQ = Collections.synchronizedList(new ArrayList<DUEntity>());
CancelEntityQ = Collections.synchronizedList(new ArrayList<DUEntity>());
close = false;
}
public void close() {
close = true;
if (threadstarted)
th.interrupt();
}
public synchronized void cancelDownload(DUEntity due) {
CancelEntityQ.add(due);
if (threadstarted)
th.interrupt();
}
public synchronized void download(DUEntity de) {
if (!DownloadEntityQ.contains(de))
DownloadEntityQ.add(de);
if (!threadstarted) {
th = new Thread(this, "DownloadHandler Thread");
th.start();
threadstarted = true;
}
}
public void notifyPassiveConnect(Socket socket) {
_socket = socket;
if (th != null)
th.interrupt();
}
public String getUserName() {
return _u.username();
}
public boolean isConnectionFailed() {
return connectionFailed;
}
public void run() {
try {
String buffer;
String params[];
try {
if (!_jdcbot.isPassive())
_socket = _jdcbot.initConnectToMe(_u.username(), "Download");
else {
buffer = "$RevConnectToMe " + _jdcbot.botname() + " " + _u.username() + "|";
logger.debug("From bot: " + buffer);
_jdcbot.SendCommand(buffer);
}
} catch (IOException be) {
logger.error("IOException in DownloadHandler thread: " + be.getMessage(), be);
connectionFailed = true;
notifyFailedConnection(new BotException(be.getMessage(), BotException.Error.IO_ERROR));
theEnd();
return;
} catch (BotException e) {
logger.error("BotException in DownloadHandler thread: " + e.getMessage(), e);
connectionFailed = true;
notifyFailedConnection(e);
theEnd();
return;
}
if (_jdcbot.isPassive()) {
int count = 0;
while (_socket == null) {
count++;
try {
Thread.sleep(60000L); //Wait for passive connection
} catch (InterruptedException e1) {
logger.debug("DownloadHandler thread woken up.");
}
if (_socket == null && count >= 3) {
logger.error("Timeout. Waited for too long for remote client's (" + _u.username() + ") connection.");
connectionFailed = true;
notifyFailedConnection(new BotException(BotException.Error.TIMEOUT));
theEnd();
return;
}
}
}
while (!DownloadEntityQ.isEmpty() && !close) {
DUEntity de = null;
de = DownloadEntityQ.get(0);
DownloadEntityQ.remove(0);
_jdcbot.getDispatchThread().callOnDownloadStart(_u, de);
boolean osClosed = false;
try {
int index;
if ((index = CancelEntityQ.indexOf(de)) != -1) {
CancelEntityQ.remove(index);
BotException e = new BotException(BotException.Error.TRANSFER_CANCELLED);
logger.error("BotException in DownloadHandler thread: " + e.getMessage(), e);
_jdcbot.getDispatchThread().callOnDownloadComplete(_u, de, false, e);
continue;
}
if (((!_u.isSupports("TTHF") || !_u.isSupports("TTHL")) && de.isSettingSet(DUEntity.AUTO_PREFIX_TTH_SETTING))
|| (!_u.isSupports("TTHL") && de.fileType() == DUEntity.Type.TTHL)) {
_jdcbot.getDispatchThread().callOnDownloadComplete(_u, de, false,
new BotException(BotException.Error.PROTOCOL_UNSUPPORTED_BY_REMOTE));
continue;
}
String file = de.file();
long start = de.start();
long fileLen = de.len();
if (de.isSettingSet(DUEntity.AUTO_PREFIX_TTH_SETTING) && de.fileType() != DUEntity.Type.FILELIST)
if (!file.startsWith("TTH/"))
file = "TTH/" + file;
boolean ZLIG = false;
try {
if (_u.isSupports("ADCGet")) {
if (de.fileType() == DUEntity.Type.FILELIST || de.fileType() == DUEntity.Type.TTHL) {
start = 0;
fileLen = -1;
}
if (de.fileType() == DUEntity.Type.FILELIST) {
//If the user wants to download the filelist.
if (_u.isSupports("XmlBZList"))
file = "files.xml.bz2";
else if (_u.isSupports("BZList"))
file = "MyList.bz2";
}
if (_u.isSupports("ZLIG")) {
buffer = "$ADCGET " + de.getFileType() + " " + file + " " + start + " " + fileLen + " ZL1|";
logger.debug("From bot: " + buffer);
SendCommand(buffer, _socket);
} else {
buffer = "$ADCGET " + de.getFileType() + " " + file + " " + start + " " + fileLen + "|";
logger.debug("From bot: " + buffer);
SendCommand(buffer, _socket);
}
buffer = ReadCommand(_socket);//Reading $ADCSND K F S LZ| OR $MaxedOut| OR $Error msg|//Z is ' ZL1' or nothing.
logger.debug(buffer);
if (buffer.equals("$MaxedOut|")) {
throw new BotException(BotException.Error.NO_FREE_SLOTS);
}
if (buffer.startsWith("$Error")) {
String err = "Transfer failed due to: " + buffer.substring(buffer.indexOf(' ') + 1, buffer.indexOf('|'));
logger.error(err);
throw new BotException(err, BotException.Error.IO_ERROR);
}
params = parseRawCmd(buffer);
fileLen = Long.parseLong(params[4]);
String Z = params[params.length - 1];
if (Z.equalsIgnoreCase("ZL1"))
ZLIG = true;
} else {
logger.error("None of known file transfer method supported by remote client.");
throw new BotException(BotException.Error.PROTOCOL_UNSUPPORTED);
}
} catch (Exception be) {
logger.error("Exception in DownloadHandler thread: " + be.getMessage(), be);
_jdcbot.getDispatchThread().callOnDownloadComplete(_u, de, false,
new BotException(be.getMessage(), BotException.Error.IO_ERROR));
continue;
}
try {
InputStream in = null;
if (ZLIG)
in = new InflaterInputStream(_socket.getInputStream());
else
in = _socket.getInputStream();
long len = 0;
int c;
InputStream fin = in;
if (de.fileType() == DUEntity.Type.FILELIST && (_u.isSupports("XmlBZList") || _u.isSupports("BZList"))
&& !de.isSettingSet(DUEntity.NO_AUTO_FILELIST_DECOMPRESS_SETTING)) {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
while ((c = in.read()) != -1 && ++len <= fileLen) {
bout.write(c);
}
byte barr[] = bout.toByteArray();
bout.reset();
//Skipping the first two bytes - BZ. Else decompression will fail and NullPoiterException maybe be thrown.
fin = new CBZip2InputStream(new ByteArrayInputStream(barr, 2, barr.length));
len = -1;
}
fin = new BufferedInputStream(fin, in_buffer_size);
int intervalCount = checkInterval;
while ((c = fin.read()) != -1 && (len == -1 || (++len <= fileLen && !close))) {
intervalCount--;
synchronized (CancelEntityQ) {
if (intervalCount <= 0 && (index = CancelEntityQ.indexOf(de)) != -1) {
CancelEntityQ.remove(index);
de.os().close();
throw new BotException(BotException.Error.TRANSFER_CANCELLED);
}
}
if (intervalCount <= 0)
intervalCount = checkInterval;
de.os().write(c);
}
de.os().close();
osClosed = true;
if (len == fileLen || len == -1)
_jdcbot.getDispatchThread().callOnDownloadComplete(_u, de, true, null);
else
_jdcbot.getDispatchThread().callOnDownloadComplete(_u, de, false,
new BotException(BotException.Error.TASK_FAILED_SHUTTING_DOWN));
} catch (IOException ioe) {
logger.error("IOException in DownloadHandler thread: " + ioe.getMessage(), ioe);
_jdcbot.getDispatchThread().callOnDownloadComplete(_u, de, false,
new BotException(ioe.getMessage(), BotException.Error.IO_ERROR));
} catch (BotException e) {
logger.error("BotException in DownloadHandler thread: " + e.getMessage(), e);
_jdcbot.getDispatchThread().callOnDownloadComplete(_u, de, false, e);
}
} finally {
try {
/* To make sure the close is always called. This is needed for proper invokation
* of onFilelistDownloadFinished() of ShareManagerListener.
*/
if (!osClosed)
de.os().close();
} catch (IOException e) {
if (e.getMessage().equals("Bad file descriptor")) {
logger.error("IOException: Bad file descriptor; in DownloadHandler.run()->de.os().write(c).\n"
+ "This thrown probably due to a known bug JDK 1.5 & 1.6. "
+ "See http://256.com/gray/docs/misc/java_bad_file_descriptor_close_bug.shtml", e);
}
}
}
}
theEnd();
} finally {
threadstarted = false;
}
}
private synchronized void theEnd() {
try {
if (_socket != null && !_socket.isClosed())
_socket.close();
} catch (IOException e) {
logger.error("IOException during closing socket in DownloadHandler thread: " + e.getMessage(), e);
}
_dm.tasksComplete(this);
}
private void notifyFailedConnection(BotException e) {
synchronized (DownloadEntityQ) {
for (DUEntity de : DownloadEntityQ)
_jdcbot.getDispatchThread().callOnDownloadComplete(_u, de, false, e);
}
}
}