package jplagWebService.serverAccess; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.HashMap; import java.util.concurrent.DelayQueue; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; import jplagWebService.server.JPlagException; import jplagWebService.server.StartResultDownloadData; import jplagWebService.serverImpl.JPlagCentral; public class TransferManager { private DelayQueue<TransferObject> uploadTimeoutQueue = new DelayQueue<TransferObject>(); private DelayQueue<TransferObject> downloadTimeoutQueue = new DelayQueue<TransferObject>(); private HashMap<String,TransferObject> userToUploadMap = new HashMap<String,TransferObject>(); private HashMap<String,TransferObject> userToDownloadMap = new HashMap<String,TransferObject>(); private boolean doStopTransferManager = false; private class TransferObject implements Delayed { private AccessStructure struct; private long timeoutTime; private File file; private int filesize; private int remainingBytes; /** * timeout is given in seconds */ TransferObject(AccessStructure struct, File file, int filesize, long timeout) { this.struct = struct; this.file = file; this.filesize = filesize; remainingBytes = filesize; setTimeout(timeout); } public long getDelay(TimeUnit unit) { long n = timeoutTime - System.nanoTime(); return unit.convert(n, TimeUnit.NANOSECONDS); } public int compareTo(Delayed o) { if(timeoutTime < ((TransferObject)o).timeoutTime) return -1; else if(timeoutTime > ((TransferObject)o).timeoutTime) return 1; else return 0; } public AccessStructure getStruct() { return struct; } public File getFile() { return file; } public int getRemainingBytes() { return remainingBytes; } /** * Writes the next part of the related file * @returns true, if this was the last part * @throws JPlagException If there is more data, than expected or * writing to the file doesn't work */ public boolean writeNextPart(byte[] data) throws JPlagException { if(remainingBytes<data.length) { throw new JPlagException("uploadException", "More data sent " + "than expected!",""); } try { FileOutputStream out = new FileOutputStream(file, true); out.write(data); out.close(); remainingBytes -= data.length; } catch(IOException e) { e.printStackTrace(); throw new JPlagException("uploadException", "Unable to save " + "submission part on the server!", "Server out of disk space??"); } return remainingBytes==0; } /** * Reads the next part of the related file * @return an array containing the next part with the correct size * @throws JPlagException If the download has already been completed * or there was an I/O error */ public byte[] readNextPart() throws JPlagException { if(remainingBytes==0) { throw new JPlagException("downloadException", "There's nothing left to be downloaded!",""); } try { FileInputStream in = new FileInputStream(file); in.skip(filesize-remainingBytes); int partsize = remainingBytes; if(partsize>81920) partsize = 81920; byte[] data = new byte[partsize]; in.read(data); in.close(); remainingBytes -= partsize; return data; } catch(IOException e) { e.printStackTrace(); throw new JPlagException("downloadException", "Unable to read" + " submission part from server!",""); } } public void setTimeout(long timeout) { timeoutTime = System.nanoTime() + timeout * 1000000000; } } /** * Checks, whether the user is already uploading a submission, and if not, * saves the first part of the entry file onto disk. If there are more * parts, the user becomes marked as uploading and the upload is added to * the upload timeout queue * @param struct AccessStructure for the according submission * @param filesize Total size of the zip file containing the submission * @param data First 80 kB part (or less, if file is less than 80 kB) * @throws JPlagException If user is already uploading, the file * already exists or saving didn't work */ public synchronized void startUpload(AccessStructure struct, int filesize, byte[] data) throws JPlagException { if(userToUploadMap.containsKey(struct.getUsername())) { throw new JPlagException("uploadException", "You are already " + "uploading a submission!", "Only one submission may be " + "uploaded by one user at a time"); } File file = new File(struct.getEntryPath()); if(file.exists()) { throw new JPlagException("uploadException", "File already exist!?", "Unable to create new file. Please tell the admins " + "about this!"); } TransferObject obj = new TransferObject(struct, file, filesize, 120); if(!obj.writeNextPart(data)) { userToUploadMap.put(struct.getUsername(), obj); uploadTimeoutQueue.add(obj); } else JPlagCentral.addToReadyQueue(struct); } /** * Writes the next part of the upload identified by the username. * If it was the last part it unmarks the user and adds the submission * to the ready queue * @param username Username of user uploading the submission * @param data Next 80 kB part (or less if it is the last part) * @throws JPlagException If saving didn't work or upload doesn't exist */ public synchronized void writeNextPart(String username, byte[] data) throws JPlagException { TransferObject obj = userToUploadMap.get(username); if(obj == null) { throw new JPlagException("uploadException", "No upload started " + "or upload timed out!", "Restart the upload"); } uploadTimeoutQueue.remove(obj); if(!obj.writeNextPart(data)) { obj.setTimeout(120); uploadTimeoutQueue.add(obj); } else { userToUploadMap.remove(username); JPlagCentral.addToReadyQueue(obj.getStruct()); } } /** * Cancels the upload with the given submissionID (if provided) for the * given user. * If the user doesn't have an upload or the submissionID doesn't match, * false is returned. */ public synchronized boolean cancelUpload(String username, String submissionID) { TransferObject obj = userToUploadMap.get(username); if(obj == null || submissionID!=null && !obj.getStruct().getSubmissionID().equals(submissionID)) { return false; } uploadTimeoutQueue.remove(obj); userToUploadMap.remove(obj.getStruct().getUsername()); obj.getFile().delete(); return true; } /** * Checks, whether the user is already downloading a submission, and if not, * loads the first part of the result file from disk. If there are more * parts, the user becomes marked as downloading and the download is added * to the download timeout queue * @param struct AccessStructure for the according submission * @return The filesize and the first part of the entry file * @throws JPlagException If user is already downloading, the file * doesn't exists or loading didn't work */ public synchronized StartResultDownloadData startDownload( AccessStructure struct) throws JPlagException { if(userToDownloadMap.containsKey(struct.getUsername())) { throw new JPlagException("downloadException", "You are already " + "downloading a submission!", "Only one submission may be " + "downloaded by one user at a time"); } File file = new File(struct.getResultPath()); if(!file.exists()) { System.out.println("startDownload: \"" + struct.getResultPath() + "\" doesn't exist!"); throw new JPlagException("downloadException", "File doesn't exist!?", "Unable to find result file!"); } int filesize = (int) file.length(); TransferObject obj = new TransferObject(struct, file, filesize, 120); byte[] data = obj.readNextPart(); if(obj.getRemainingBytes()>0) { userToDownloadMap.put(struct.getUsername(), obj); downloadTimeoutQueue.add(obj); } else JPlagCentral.cancelSubmission(struct); return new StartResultDownloadData(filesize, data); } /** * Reads the next part of the download identified by the username. * If it was the last part it unmarks the user and deletes the entry file * @param username Username of user downloading the result * @return data Next 80 kB part (or less if it is the last part) * @throws JPlagException If loading didn't work or download doesn't exist */ public synchronized byte[] readNextPart(String username) throws JPlagException { TransferObject obj = userToDownloadMap.get(username); if(obj == null) { throw new JPlagException("downloadException", "No download started or download timed out!", "Restart the download"); } downloadTimeoutQueue.remove(obj); byte[] data = obj.readNextPart(); if(obj.getRemainingBytes()!=0) { obj.setTimeout(120); downloadTimeoutQueue.add(obj); } else { userToDownloadMap.remove(username); JPlagCentral.cancelSubmission(obj.getStruct()); } return data; } /** * Cancels the download with the given submissionID (if provided) for the * given user and deletes the all files related to the submission. * If the user doesn't have a download or the submissionID doesn't match, * false is returned. */ public synchronized boolean cancelDownload(String username, String submissionID) { TransferObject obj = userToDownloadMap.get(username); if(obj == null || submissionID!=null && !obj.getStruct().getSubmissionID().equals(submissionID)) { return false; } downloadTimeoutQueue.remove(obj); userToDownloadMap.remove(obj.getStruct().getUsername()); JPlagCentral.cancelSubmission(obj.getStruct()); return true; } /** * Cancels a transfer for the user with the given submissionID if existing. * @return true, if the transfer got cancelled */ public boolean cancelTransfer(String username, String submissionID) { if(cancelUpload(username, submissionID)) return true; return cancelDownload(username, submissionID); } public void stopTransferManager() { doStopTransferManager = true; } /** * Waits for the next uploaded transfer object to timeout and removes it */ private Thread uploadThread = new Thread() { public void run() { try { while(!doStopTransferManager) { TransferObject obj = null; try { obj = uploadTimeoutQueue.take(); } catch(InterruptedException ex) { continue; } // Upload timed out, so delete already uploaded part synchronized(this) { obj.getFile().delete(); userToUploadMap.remove(obj.getStruct().getUsername()); } } } catch(Exception ex) { ex.printStackTrace(); } System.out.println("Upload TransferManager stopped!"); } }; /** * Waits for the next downloaded transfer object to timeout and removes it */ private Thread downloadThread = new Thread() { public void run() { try { while(!doStopTransferManager) { TransferObject obj = null; try { obj = downloadTimeoutQueue.take(); } catch(InterruptedException ex) { continue; } // Download timed out, so cancel download synchronized(this) { userToDownloadMap.remove(obj.getStruct().getUsername()); } } } catch(Exception ex) { ex.printStackTrace(); } System.out.println("Download TransferManager stopped!"); } }; public void start() { uploadThread.start(); downloadThread.start(); } public boolean isAlive() { return uploadThread.isAlive() || downloadThread.isAlive(); } public void interrupt() { uploadThread.interrupt(); downloadThread.interrupt(); } }