307 lines
8.1 KiB
Java
307 lines
8.1 KiB
Java
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 exception.InternalError;
|
|
import remoteException.EmptyDirectory;
|
|
import remoteException.EmptyFile;
|
|
import exception.ProtocolError;
|
|
import remoteException.InternalRemoteError;
|
|
import remoteException.VersionRemoteError;
|
|
import exception.TransmissionError;
|
|
import remoteException.ProtocolRemoteError;
|
|
import exception.VersionError;
|
|
import exception.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<Long> toDoTasks;
|
|
private List<Long> pendingTasks;
|
|
private List<Long> 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<Long> getFailed() {
|
|
List<Long> 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<Long> getDone() throws InterruptedException {
|
|
if (tasksDone.size() == 0) {
|
|
return new ArrayList<>();
|
|
} else {
|
|
synchronized (this) {
|
|
while(tasksListsLock) {
|
|
this.wait();
|
|
}
|
|
tasksListsLock = true;
|
|
List<Long> 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;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
public 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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
}
|