package clientP2P; import java.util.List; import java.util.ArrayList; import java.net.DatagramSocket; import protocolP2P.ProtocolP2PPacketUDP; import protocolP2P.Payload; import protocolP2P.LoadRequest; import protocolP2P.FilePart; import localException.InternalError; import remoteException.EmptyDirectory; import remoteException.EmptyFile; import localException.ProtocolError; import remoteException.InternalRemoteError; import remoteException.VersionRemoteError; import localException.TransmissionError; import remoteException.ProtocolRemoteError; import localException.VersionError; import localException.SizeError; import remoteException.NotFound; import java.nio.file.Files; import java.io.File; import java.nio.file.Paths; import java.io.IOException; import tools.Logger; import tools.LogLevel; /** Class to download file parts on udp. * @author Louis Royer * @author Flavien Haas * @author JS Auge * @version 1.0 */ public class ClientDownloadPartUDP implements Runnable { private List toDoTasks; private List pendingTasks; private List tasksDone; private volatile boolean tasksListsLock; private volatile boolean stop; private volatile boolean failed; private String filename; private DatagramSocket socket; private volatile boolean noTask; private String partsSubdir; private static final long MAX_PARTIAL_SIZE = 4096; private ClientDownloadUDP manager; private Logger logger; /** Constructor with filename, socket, and part subdir * @param filename name of file to download * @param socket socket to use * @param partsSubdir directory to store .part files */ public ClientDownloadPartUDP(ClientDownloadUDP manager, String filename, DatagramSocket socket, String partsSubdir, Logger logger) { this.manager = manager; this.partsSubdir = partsSubdir; this.filename = filename; this.socket = socket; this.logger = logger; stop = false; failed = false; pendingTasks = new ArrayList<>(); toDoTasks = new ArrayList<>(); tasksDone = new ArrayList<>(); noTask = true; tasksListsLock = false; } /** True if thread has failed to get a file. * @return true if thread has failed to get a file */ public boolean hasFailed() { return failed; } /** Asks to stop thread. * @throws InterruptedException */ public synchronized void setStop() throws InterruptedException { stop = true; this.notifyAll(); } /** Runnable implementation */ public void run() { while(!stop) { try { doTasks(); synchronized(manager) { manager.notify(); } } catch(InterruptedException e) { try { setStop(); synchronized(manager) { manager.notify(); } } catch (InterruptedException e2) { } } } System.err.println("Closing socket"); logger.writeUDP("Closing socket", LogLevel.Info); socket.close(); } /** Get list of offsets that have not be downloaded if failed, else * empty list. * @return list of offsets */ public List getFailed() { List ret = new ArrayList<>(); if (failed) { ret.addAll(pendingTasks); ret.addAll(toDoTasks); } return ret; } /** Get list of downloaded file parts offset, then clear this list. * @return list of offsets * @throws InterruptedException */ public List getDone() throws InterruptedException { if (tasksDone.size() == 0) { return new ArrayList<>(); } else { synchronized (this) { while(tasksListsLock) { this.wait(); } tasksListsLock = true; List ret = new ArrayList<>(tasksDone); tasksDone.clear(); tasksListsLock = false; this.notifyAll(); return ret; } } } /** Adds offset of files parts to download. * @param task offset to download * @throws InterruptedException */ public synchronized void assignTask(Long task) throws InterruptedException { synchronized(this) { while(tasksListsLock) { this.wait(); } tasksListsLock = true; toDoTasks.add(task); noTask = false; tasksListsLock = false; this.notifyAll(); } } /** Send one request and wait for one response. Blocks when no task. * @throws InterruptedException */ public synchronized void doTasks() throws InterruptedException { while(noTask && !stop) { this.wait(); } if (!stop) { try { Long offset = toDoTasks.get(0); ProtocolP2PPacketUDP p = reqPart(offset); if (p == null) { stop = true; } failed = downloadPart(p); if (failed) { System.err.println("Error: DownloadPart failed."); stop = true; } else if (toDoTasks.isEmpty()) { noTask = true; } } catch (IndexOutOfBoundsException e) { logger.writeUDP(e, LogLevel.Error); noTask = true; } } } /** Send a request for a specific offset. * @param offset Offset of the file part to download * @return ProtocolP2PPacketTCP used to send request */ private ProtocolP2PPacketUDP reqPart(Long offset) { System.err.println("New request: "+ offset); logger.writeUDP("New request: "+ offset, LogLevel.Info); // maintain tracking of tasks if (toDoTasks.contains(offset)) { try { synchronized (this) { while(tasksListsLock) { this.wait(); } tasksListsLock = true; toDoTasks.remove(offset); pendingTasks.add(offset); tasksListsLock = false; this.notifyAll(); } } catch(InterruptedException e) { System.err.println("Error: reqPart interruptedException"); logger.writeUDP("reqPart interruptedException", LogLevel.Error); return null; } } else { System.err.println("Error: reqPart (offset " + offset + " not in toDoTasks)"); logger.writeUDP("reqPart (offset " + offset + " not in toDoTasks)", LogLevel.Error); return null; } // send request try { ProtocolP2PPacketUDP d = new ProtocolP2PPacketUDP((Payload) new LoadRequest(filename, offset.longValue(), MAX_PARTIAL_SIZE)); d.sendRequest((Object)socket); return d; } catch (InternalError e) { System.err.println("Error: reqPart internalError"); logger.writeUDP("reqPart internalError", LogLevel.Error); return null; } catch (IOException e) { e.printStackTrace(); System.err.println("Error: reqPart ioexception"); logger.writeUDP("reqPart ioexception", LogLevel.Error); return null; } } /** Download file part associated to the request send (d). * @param d request packet * @return true on failure, else false */ public boolean downloadPart(ProtocolP2PPacketUDP d) { if (d == null) { System.err.println("Error: downloadPart -> d is null."); logger.writeUDP("downloadPart -> d is null.", LogLevel.Error); return true; } try { Payload p = d.receiveResponse().getPayload(); assert p instanceof FilePart : "This payload must be instance of FilePart"; if (!(p instanceof FilePart)) { System.err.println("Error: cannot get size."); logger.writeUDP("cannot get size.", LogLevel.Error); return true; } else { FilePart fp = (FilePart)p; if (!fp.getFilename().equals(filename)) { System.err.println("Error: wrong file received: `" + fp.getFilename() + "`"); logger.writeUDP("wrong file received: `" + fp.getFilename() + "`", LogLevel.Error); return true; } Long offset = Long.valueOf(fp.getOffset()); if (pendingTasks.contains(offset)) { try { Files.write(new File(partsSubdir + filename + "_" + offset + ".part").toPath(), fp.getPartialContent()); } catch (IOException e) { System.err.println("Error: cannot write file (" + partsSubdir + filename + "_" + offset + ".part)"); logger.writeUDP("cannot write file (" + partsSubdir + filename + "_" + offset + ".part)", LogLevel.Error); } } else { System.err.println("Error: wrong file part received."); logger.writeUDP("wrong file part received.", LogLevel.Error); return true; } try { synchronized(this) { while(tasksListsLock) { this.wait(); } tasksListsLock = true; pendingTasks.remove(offset); tasksDone.add(offset); tasksListsLock = false; this.notifyAll(); } } catch(InterruptedException e) { System.err.println("Error: DownloadPart Interrupted exception"); logger.writeUDP("DownloadPart Interrupted exception", LogLevel.Error); return true; } } } catch (EmptyDirectory e) { System.err.println("Error: empty directory."); logger.writeUDP("empty directory.", LogLevel.Error); return true; } catch (EmptyFile e) { System.err.println("Error: downloadPart emptyFile"); logger.writeUDP("downloadPart emptyFile", LogLevel.Error); // TODO: use more specific errors return true; } catch (ProtocolError e) { System.err.println("Error: downloadPart protocolError"); logger.writeUDP("downloadPart protocolError", LogLevel.Error); return true; } catch (InternalRemoteError e) { System.err.println("Error: downloadPart internalRemoteError"); logger.writeUDP("downloadPart internalRemoteError", LogLevel.Error); return true; } catch (VersionRemoteError e) { System.err.println("Error: downloadPart versionRemoteError"); logger.writeUDP("downloadPart versionRemoteError", LogLevel.Error); return true; } catch (ProtocolRemoteError e) { System.err.println("Error: downloadPart protocolRemoteError"); logger.writeUDP("downloadPart protocolRemoteError", LogLevel.Error); return true; } catch (TransmissionError e) { System.err.println("Error: downloadPart transmissionError"); logger.writeUDP("downloadPart transmissionError", LogLevel.Error); return true; } catch (VersionError e) { System.err.println("Error: downloadPart versionError"); logger.writeUDP("downloadPart versionError", LogLevel.Error); return true; } catch (SizeError e) { System.err.println("Error: downloadPart sizeError"); logger.writeUDP("downloadPart sizeError", LogLevel.Error); return true; } catch (NotFound e) { System.err.println("Error: downloadPart notFound"); logger.writeUDP("downloadPart notFound", LogLevel.Error); return true; } catch (IOException e) { System.err.println("Error: downloadPart ioexception"); logger.writeUDP("downloadPart ioexception", LogLevel.Error); return true; } catch (InternalError e) { System.err.println("Error: downloadPart internalError"); logger.writeUDP("downloadPart internalError", LogLevel.Error); return true; } return false; } }