/* * UploadHandler.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.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.zip.DeflaterOutputStream; import org.elite.jdcbot.shareframework.ShareManager; import org.slf4j.Logger; /** * Created on 26-May-08<br> * Handles all the uploads to a single user for a session. * <p> * The framework manages this hence you need not bother * about this. * * @author AppleGrew * @since 0.7 * @version 0.1.3 */ public class UploadHandler extends InputThreadTarget { private static final Logger logger = GlobalObjects.getLogger(UploadHandler.class); private final int BUFF_SIZE = 64 * 1024; //64 KB private Socket socket; private UploadManager um; private jDCBot jdcbot; private ShareManager sm; private TimeoutInputThread inputThread = null; /** * This variable is important so fair upload * policy can be enforced. It would be better * to explain using an example. * <p> * If suppose <b>S</b> is a special user who has * been granted an extra slot, and <b>N</b> is a normal * user who has no extra slot granted to him. * <p> * If we have only one upload slot open and <b>N</b> starts * a download first and now <b>S</b> later tries to download; * if we donot use this variable then running upload of * <b>N</b> will be interrupted by <b>S</b> and <b>N</b> will suddenly get * the no free slots error. This is unfair to <b>N</b>. * <p> * Another possible implementation without using this * variable too <i>seems</i> good. It is that we instead * query from UploadManager about number of running uploads * to normal users only. But, this too has a problem of * being too liberal about uploading. Suppose if we have * only one upload slot open and <b>S</b> is already * downloading from us, if at that moment if <b>N</b> * tries to download then he too would be allowed to * download. This time refusing to user <b>N</b> wouldn't * have been rude, yet we are alowing two uploads instead * of one. */ private boolean isfirstUpload = true; private User user; private volatile boolean close; private volatile boolean cancelUpload; UploadHandler(User usr, Socket socket, jDCBot jdcbot, UploadManager um) { this.um = um; this.socket = socket; this.jdcbot = jdcbot; sm = jdcbot.getShareManager(); user = usr; close = false; cancelUpload = false; } public void startUploads() { if (inputThread == null) { try { inputThread = new TimeoutInputThread(this, socket.getInputStream()); } catch (IOException e) { logger.error("IOException by socket.getInputStream() in startUploads(): " + e.getMessage(), e); } cancelUpload = false; isfirstUpload = true; inputThread.start(); } } public String getUserName() { return user.username(); } public void close() throws IOException { inputThread.stop(); close = true; if (socket != null & !socket.isClosed()) socket.close(); socket = null; } /** * Cancels the currently running upload. * It won't affect the next upload to the user. * Infact if the remote client retries to get * that file again then it won't affect that * attempt. You will need to invoke this * mathod again then. */ public void cancelUpload() { cancelUpload = true; } @Override public void handleCommand(String cmd) { upload(cmd); } public void upload(String cmd) { //Called by inputThread thread only. if (socket == null) { cancelUpload = false; return; } logger.debug("From remote client:" + cmd); boolean ZLIG = false; InputStream in = null; OutputStream os = null; long fileLen = 0; String buffer; DUEntity due = null; try { if (cmd.startsWith("$ADCGET")) { String params[] = parseRawCmd(cmd); //Parsing $ADCGET F S LZ then //Z is ' ZL1' or nothing String fileType = params[1].toLowerCase().trim(); String file = params[2]; long start = Long.parseLong(params[3]); fileLen = Long.parseLong(params[4]); String Z = params[params.length - 1]; //Using this weird method because Z may or may not be present. if (Z.equalsIgnoreCase("ZL1")) ZLIG = true; DUEntity.Type fType = DUEntity.Type.FILE; if (fileType.equals("file")) { if (file.equals("files.xml.bz2") || file.equals("MyList.bz2")) fType = DUEntity.Type.FILELIST; else fType = DUEntity.Type.FILE; } else if (fileType.equals("tthl")) fType = DUEntity.Type.TTHL; if (isfirstUpload && fType != DUEntity.Type.FILELIST && !user.isGrantedExtraSlot() && jdcbot.getFreeUploadSlots() <= 0) { buffer = "$MaxedOut|"; try { SendCommand(buffer, socket); logger.debug("From bot: " + buffer); } catch (Exception e) { logger.error("Exception by SendCommand in upload(): " + e.getMessage(), e); } finally { try { socket.close(); } catch (IOException e) { logger.error("Exception by socket.close() in upload(): " + e.getMessage(), e); } } cancelUpload = false; return; } if (fType != DUEntity.Type.FILELIST) isfirstUpload = false; try { if (fType == DUEntity.Type.FILELIST) due = sm.getFileList(user); else due = sm.getFile(user, file, fType, start, fileLen); } catch (FileNotFoundException e1) { buffer = "$Error " + e1.getMessage() + "|"; try { SendCommand(buffer, socket); } catch (Exception e) { logger.error("Exception by SendCommand in upload(): " + e.getMessage(), e); } logger.debug("From bot: " + buffer); cancelUpload = false; return; } try { in = due.in(); fileLen = due.len(); if (ZLIG) { buffer = "$ADCSND " + due.getFileType() + " " + file + " " + due.start() + " " + due.len() + " ZL1|"; SendCommand(buffer, socket); logger.debug("From bot: " + buffer); } else { buffer = "$ADCSND " + due.getFileType() + " " + file + " " + due.start() + " " + due.len() + "|"; SendCommand(buffer, socket); logger.debug("From bot: " + buffer); } } catch (Exception e) { logger.error("Exception by SendCommand in upload(): " + e.getMessage(), e); cancelUpload = false; return; } } else { logger.warn("Unsupported protocol requested by remote client. Command sent was: " + cmd); cancelUpload = false; return; } try { if (ZLIG) os = new DeflaterOutputStream(socket.getOutputStream()); else os = socket.getOutputStream(); } catch (IOException e) { logger.error("IOException while getting OutputStream of the socket in upload(): " + e.getMessage(), e); cancelUpload = false; return; } if (in != null && os != null) { jdcbot.getDispatchThread().callOnUploadStart(user, due); int c; byte buff[] = new byte[BUFF_SIZE]; try { while ((c = in.read(buff)) != -1 && !close) { if (cancelUpload) { throw new BotException(BotException.Error.TRANSFER_CANCELLED); } os.write(buff, 0, c); } in.close(); if (os instanceof DeflaterOutputStream) ((DeflaterOutputStream) os).finish(); else os.flush(); jdcbot.getDispatchThread().callOnUploadComplete(user, due, true, null); } catch (IOException ioe) { logger.error("IOException in startUploads(): " + ioe.getMessage(), ioe); jdcbot.getDispatchThread().callOnUploadComplete(user, due, false, new BotException(ioe.getMessage(), BotException.Error.IO_ERROR)); } catch (BotException e) { jdcbot.getDispatchThread().callOnUploadComplete(user, due, false, e); } cancelUpload = false; } else { logger.info("InputStream or OutputStream was null hence no data was sent. IN==" + in + ", OUT==" + os); } } finally { if (in != null) try { in.close(); } catch (IOException e) { logger.error("Exception in upload()", e); } cancelUpload = false; } } @Override public void disconnected() { um.tasksComplete(this); } }