package clientP2P; import java.util.List; import java.util.ArrayList; import java.io.File; import java.io.IOException; import java.nio.file.Files; import protocolP2P.ProtocolP2PPacket; import protocolP2P.Payload; import protocolP2P.LoadRequest; import protocolP2P.FilePart; import localException.InternalError; import localException.ProtocolError; import localException.TransmissionError; import localException.VersionError; import localException.SizeError; import localException.SocketClosed; import remoteException.EmptyDirectory; import remoteException.EmptyFile; import remoteException.InternalRemoteError; import remoteException.VersionRemoteError; import remoteException.ProtocolRemoteError; import remoteException.NotFound; import remoteException.NotATracker; import exception.LocalException; import exception.RemoteException; import tools.Logger; import tools.LogLevel; import tools.ServeErrors; /** Class to download file parts. * @author Louis Royer * @author Flavien Haas * @author JS Auge * @version 1.0 */ public abstract class ClientDownloadPart extends ServeErrors implements Runnable { protected List toDoTasks; protected List pendingTasks; protected List tasksDone; protected volatile boolean tasksListsLock; protected volatile boolean stop; protected volatile boolean failed; protected String filename; protected volatile boolean noTask; protected String partsSubdir; protected static final long MAX_PARTIAL_SIZE = 4096; protected ClientDownload manager; protected 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 * @param logger Logger */ public ClientDownloadPart(ClientDownload manager, String filename, String partsSubdir, Logger logger) { this.manager = manager; this.partsSubdir = partsSubdir; this.filename = filename; 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) { } } } writeLog("Closing socket", LogLevel.Info); try{ closeSocket(); } catch(IOException e){ writeLog("can't close socket", LogLevel.Error); } } /** Close the socket */ protected abstract void closeSocket() throws IOException; /** 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); ProtocolP2PPacket p = reqPart(offset); if (p == null) { stop = true; } failed = downloadPart(p); if (failed) { System.err.println("Error: DownloadPart failed."); writeLog("DownloadPart failed.", LogLevel.Error); stop = true; } else if (toDoTasks.isEmpty()) { noTask = true; } } catch (IndexOutOfBoundsException e) { writeLog(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 */ protected ProtocolP2PPacket reqPart(Long offset) { writeLog("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) { writeLog("reqPart interruptedException", LogLevel.Error); return null; } } else { writeLog("reqPart (offset " + offset + " not in toDoTasks)", LogLevel.Error); return null; } // send request try { ProtocolP2PPacket d = createProtocolP2PPacket(new LoadRequest(filename, offset.longValue(), MAX_PARTIAL_SIZE)); d.sendRequest(getSocket()); return d; } catch (InternalError e) { writeLog("reqPart internalError", LogLevel.Error); return null; } catch (IOException e) { writeLog("reqPart ioexception", LogLevel.Error); writeLog(e, LogLevel.Error); return null; } catch (SocketClosed e){ writeLog("reqPart SocketClosed", LogLevel.Error); return null; } } /** Get the socket */ protected abstract Object getSocket(); /** Download file part associated to the request send (d). * @param d request packet * @return true on failure, else false */ public < T extends ProtocolP2PPacket > boolean downloadPart(T d) { if (d == null) { writeLog("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)) { writeLog("cannot get size.", LogLevel.Error); return true; } else { FilePart fp = (FilePart)p; if (!fp.getFilename().equals(filename)) { writeLog("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) { writeLog("cannot write file (" + partsSubdir + filename + "_" + offset + ".part)", LogLevel.Error); } } else { writeLog("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) { writeLog("DownloadPart Interrupted exception", LogLevel.Error); return true; } } } catch (LocalException e) { writeLog(e, LogLevel.Error); return true; } catch (RemoteException e) { writeLog(e, LogLevel.Error); return true; } catch (IOException e) { System.err.println("Error: downloadPart ioexception"); writeLog("downloadPart ioexception", LogLevel.Error); return true; } return false; } }