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; /** 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; /** 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) { this.manager = manager; this.partsSubdir = partsSubdir; this.filename = filename; this.socket = socket; 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"); 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) { 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); // 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"); return null; } } else { System.err.println("Error: reqPart (offset " + offset + " not in toDoTasks)"); 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"); return null; } catch (IOException e) { e.printStackTrace(); System.err.println("Error: reqPart ioexception"); 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."); 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."); return true; } else { FilePart fp = (FilePart)p; if (!fp.getFilename().equals(filename)) { System.err.println("Error: wrong file received: `" + fp.getFilename() + "`"); 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)"); } } else { System.err.println("Error: wrong file part received."); 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"); return true; } } } catch (EmptyDirectory e) { System.err.println("Error: empty directory."); return true; } catch (EmptyFile e) { System.err.println("Error: downloadPart emptyFile"); // TODO: use more specific errors return true; } catch (ProtocolError e) { System.err.println("Error: downloadPart protocolError"); return true; } catch (InternalRemoteError e) { System.err.println("Error: downloadPart internalRemoteError"); return true; } catch (VersionRemoteError e) { System.err.println("Error: downloadPart versionRemoteError"); return true; } catch (ProtocolRemoteError e) { System.err.println("Error: downloadPart protocolRemoteError"); return true; } catch (TransmissionError e) { System.err.println("Error: downloadPart transmissionError"); return true; } catch (VersionError e) { System.err.println("Error: downloadPart versionError"); return true; } catch (SizeError e) { System.err.println("Error: downloadPart sizeError"); return true; } catch (NotFound e) { System.err.println("Error: downloadPart notFound"); return true; } catch (IOException e) { System.err.println("Error: downloadPart ioexception"); return true; } catch (InternalError e) { System.err.println("Error: downloadPart internalError"); return true; } return false; } }