Louis Royer
039c3ff2b0
All checks were successful
flavien's git/Projet_JAVA_P2P_STRI2A/pipeline/head This commit looks good
Fix #17
353 lines
9.3 KiB
Java
353 lines
9.3 KiB
Java
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 protocolP2P.Denied;
|
|
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;
|
|
import tools.HostItem;
|
|
|
|
/** 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 long receivedBytesCount;
|
|
protected List<Long> toDoTasks;
|
|
protected List<Long> pendingTasks;
|
|
protected List<Long> 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;
|
|
protected HostItem client;
|
|
private HostItem server;
|
|
|
|
/** 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
|
|
* @param client HostItem of the application
|
|
* @param server HostItem of the server application
|
|
*/
|
|
public ClientDownloadPart(ClientDownload manager, String filename, String partsSubdir, Logger logger, HostItem client, HostItem server) {
|
|
this.manager = manager;
|
|
this.partsSubdir = partsSubdir;
|
|
this.filename = filename;
|
|
this.logger = logger;
|
|
this.client = client;
|
|
this.server = server;
|
|
stop = false;
|
|
failed = false;
|
|
pendingTasks = new ArrayList<>();
|
|
toDoTasks = new ArrayList<>();
|
|
tasksDone = new ArrayList<>();
|
|
noTask = true;
|
|
tasksListsLock = false;
|
|
receivedBytesCount = 0;
|
|
}
|
|
|
|
|
|
/** receivedBytesCount getter
|
|
* @return receivedBytesCount
|
|
*/
|
|
public Long getReceivedBytesCount() {
|
|
return Long.valueOf(receivedBytesCount);
|
|
}
|
|
|
|
/** Server getter
|
|
* @return server
|
|
*/
|
|
public HostItem getServer() {
|
|
return server;
|
|
}
|
|
|
|
/** 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.notifyAll();
|
|
}
|
|
} catch(InterruptedException e) {
|
|
try {
|
|
setStop();
|
|
synchronized(manager) {
|
|
manager.notifyAll();
|
|
}
|
|
} 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<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);
|
|
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.Debug);
|
|
// 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, client));
|
|
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();
|
|
if (p instanceof Denied) {
|
|
Denied denied = (Denied)p;
|
|
if (!denied.getFilename().equals(filename)) {
|
|
writeLog("wrong file deny response received: `" + denied.getFilename() + "`", LogLevel.Error);
|
|
return true;
|
|
}
|
|
Long offset = Long.valueOf(denied.getOffset());
|
|
if (pendingTasks.contains(offset)) {
|
|
pendingTasks.remove(offset);
|
|
toDoTasks.add(offset);
|
|
return false;
|
|
} else {
|
|
writeLog("wrong file offset deny received: " + offset, LogLevel.Error);
|
|
return true;
|
|
}
|
|
}
|
|
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)) {
|
|
byte[] partialContent = fp.getPartialContent();
|
|
try {
|
|
Files.write(new File(partsSubdir + filename + "_" + offset + ".part").toPath(), partialContent);
|
|
receivedBytesCount += partialContent.length;
|
|
} 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;
|
|
}
|
|
|
|
|
|
}
|