Fix #40
parent
275eb165b1
commit
1355ef14b1
@ -0,0 +1,409 @@
|
|||||||
|
package clientP2P;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import exception.LocalException;
|
||||||
|
import exception.RemoteException;
|
||||||
|
import localException.SocketClosed;
|
||||||
|
import localException.ProtocolError;
|
||||||
|
import localException.InternalError;
|
||||||
|
import localException.TransmissionError;
|
||||||
|
import localException.SizeError;
|
||||||
|
import localException.VersionError;
|
||||||
|
import remoteException.EmptyDirectory;
|
||||||
|
import remoteException.EmptyFile;
|
||||||
|
import remoteException.VersionRemoteError;
|
||||||
|
import remoteException.ProtocolRemoteError;
|
||||||
|
import remoteException.NotFound;
|
||||||
|
import remoteException.InternalRemoteError;
|
||||||
|
import remoteException.NotATracker;
|
||||||
|
import protocolP2P.HashAlgorithm;
|
||||||
|
import protocolP2P.HashResponse;
|
||||||
|
import protocolP2P.HashRequest;
|
||||||
|
import protocolP2P.Payload;
|
||||||
|
import protocolP2P.FilePart;
|
||||||
|
import protocolP2P.LoadRequest;
|
||||||
|
import protocolP2P.ProtocolP2PPacket;
|
||||||
|
import clientP2P.ClientDownloadPart;
|
||||||
|
import tools.HostItem;
|
||||||
|
import tools.Logger;
|
||||||
|
import tools.LogLevel;
|
||||||
|
import tools.ServeErrors;
|
||||||
|
|
||||||
|
/** Class to download file
|
||||||
|
* @author Louis Royer
|
||||||
|
* @author Flavien Haas
|
||||||
|
* @author JS Auge
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
public abstract class ClientDownload extends ServeErrors implements Runnable {
|
||||||
|
protected List<HostItem> hostList;
|
||||||
|
protected String filename;
|
||||||
|
protected byte[] hash512;
|
||||||
|
protected List<ClientDownloadPart> sockList = new ArrayList<ClientDownloadPart>();
|
||||||
|
protected List<Long> offsetsToAsk = new ArrayList<Long>();
|
||||||
|
protected List<Long> offsetsPending = new ArrayList<Long>();
|
||||||
|
protected boolean stop;
|
||||||
|
protected long size;
|
||||||
|
protected static final long MAX_PARTIAL_SIZE = 4096;
|
||||||
|
protected String partsSubdir;
|
||||||
|
protected String dirStorage;
|
||||||
|
protected boolean success = false;
|
||||||
|
protected Logger logger;
|
||||||
|
|
||||||
|
/** Constructor with parameters: filename, list of hosts, parts subdirectory and dirStorage
|
||||||
|
* @param filename name of file to download
|
||||||
|
* @param hostList list of servers
|
||||||
|
* @param partsSubdir directory to store .part files
|
||||||
|
* @param dirStorage directory to write assembled file
|
||||||
|
* @param logger Logger
|
||||||
|
*/
|
||||||
|
public ClientDownload(String filename, List<HostItem> hostList, String partsSubdir, String dirStorage, Logger logger) {
|
||||||
|
this.partsSubdir = partsSubdir;
|
||||||
|
this.dirStorage = dirStorage;
|
||||||
|
this.filename = filename;
|
||||||
|
this.hostList = hostList;
|
||||||
|
this.logger = logger;
|
||||||
|
this.stop = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Success getter.
|
||||||
|
* @return true when file have successfully been reassembled.
|
||||||
|
*/
|
||||||
|
public boolean getSuccess() {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Getter for hash512sum
|
||||||
|
* @return hash512sum
|
||||||
|
*/
|
||||||
|
public byte[] getHashSum512() {
|
||||||
|
return hash512;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Stop threads */
|
||||||
|
protected void stopTasks() {
|
||||||
|
for(ClientDownloadPart c : sockList) {
|
||||||
|
try {
|
||||||
|
c.setStop();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
writeLog(e, LogLevel.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Asks thread to stop
|
||||||
|
*/
|
||||||
|
public void setStop() {
|
||||||
|
stop = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Assign tasks randomly to threads.
|
||||||
|
* @throws InternalError
|
||||||
|
*/
|
||||||
|
protected void assignTasks() throws InternalError {
|
||||||
|
Random rand = new Random();
|
||||||
|
for(long offset : offsetsToAsk) {
|
||||||
|
try {
|
||||||
|
sockList.get(rand.nextInt(sockList.size())).assignTask(offset);
|
||||||
|
offsetsPending.add(offset);
|
||||||
|
System.err.println("Assigned task "+ offset);
|
||||||
|
writeLog("Assigned task "+ offset, LogLevel.Info);
|
||||||
|
} catch(InterruptedException e) {
|
||||||
|
writeLog(e, LogLevel.Error);
|
||||||
|
throw new InternalError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offsetsToAsk.removeAll(offsetsPending);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a clientDownloadPart
|
||||||
|
* @param filename name of the file to download
|
||||||
|
* @param hostItem Hostitem of the server
|
||||||
|
*/
|
||||||
|
protected abstract ClientDownloadPart createDownloadPart(String filename, HostItem hostItem);
|
||||||
|
|
||||||
|
/** Starts threads for each server in hostList.
|
||||||
|
*/
|
||||||
|
protected void initThreads() {
|
||||||
|
for(HostItem hostItem: hostList) {
|
||||||
|
sockList.add(createDownloadPart(filename, hostItem));
|
||||||
|
}
|
||||||
|
for(ClientDownloadPart c: sockList) {
|
||||||
|
Thread t = new Thread(c);
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
writeLog("Threads initialized", LogLevel.Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Remove tasks from failed threads. Update done status.
|
||||||
|
* @throws InternalError
|
||||||
|
*/
|
||||||
|
protected void checkTasksStatus() throws InternalError {
|
||||||
|
try {
|
||||||
|
synchronized(this) {
|
||||||
|
this.wait();
|
||||||
|
List<ClientDownloadPart> sockListCpy = new ArrayList<>(sockList);
|
||||||
|
for(ClientDownloadPart c: sockListCpy) {
|
||||||
|
if (c.hasFailed() == true) {
|
||||||
|
sockList.remove(c);
|
||||||
|
offsetsPending.removeAll(c.getFailed());
|
||||||
|
offsetsToAsk.addAll(c.getFailed());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
offsetsPending.removeAll(c.getDone());
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new InternalError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeLog("Task check status: " + offsetsToAsk.size() + " to asks, " + offsetsPending.size() + " pending", LogLevel.Info);
|
||||||
|
if (offsetsToAsk.isEmpty() && offsetsPending.isEmpty()) {
|
||||||
|
stop = true;
|
||||||
|
}
|
||||||
|
if (sockList.size() == 0) {
|
||||||
|
logger.writeUDP("No thread working", LogLevel.Error);
|
||||||
|
throw new InternalError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new InternalError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get hashsum from server.
|
||||||
|
* @param hostItem server to ask hash
|
||||||
|
* @return hash512sum
|
||||||
|
* @throws InternalError
|
||||||
|
*/
|
||||||
|
protected byte[] getHashSum512(HostItem hostItem) throws InternalError {
|
||||||
|
byte[] hash;
|
||||||
|
HashAlgorithm[] hashesAlgo = new HashAlgorithm[1];
|
||||||
|
hashesAlgo[0] = HashAlgorithm.SHA512;
|
||||||
|
ProtocolP2PPacket<?> d = createProtocolP2PPacket(new HashRequest(filename, hashesAlgo));
|
||||||
|
try {
|
||||||
|
d.sendRequest(getHostItemSocket(hostItem));
|
||||||
|
try {
|
||||||
|
Payload pHash = d.receiveResponse().getPayload();
|
||||||
|
assert pHash instanceof HashResponse : "This payload must be instance of HashResponse";
|
||||||
|
if (!(pHash instanceof HashResponse)) {
|
||||||
|
throw new InternalError();
|
||||||
|
} else {
|
||||||
|
hash = ((HashResponse)pHash).getHash(HashAlgorithm.SHA512);
|
||||||
|
}
|
||||||
|
} catch (EmptyDirectory e) {
|
||||||
|
writeLog(e, LogLevel.Error);
|
||||||
|
hash = new byte[0];
|
||||||
|
} catch (NotFound e) {
|
||||||
|
writeLog(e, LogLevel.Error);
|
||||||
|
hash = new byte[0];
|
||||||
|
} catch (LocalException e) {
|
||||||
|
writeLog(e, LogLevel.Error);
|
||||||
|
throw new InternalError();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
writeLog(e, LogLevel.Error);
|
||||||
|
throw new InternalError();
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
} catch (IOException e) {
|
||||||
|
writeLog(e, LogLevel.Error);
|
||||||
|
throw new InternalError();
|
||||||
|
} catch (SocketClosed e){
|
||||||
|
System.err.println("getHashSum512 : SocketClosed");
|
||||||
|
writeLog("getHashSum512 : SocketClosed", LogLevel.Error);
|
||||||
|
throw new InternalError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Removes servers not owning the correct file to download from list.
|
||||||
|
* This is done by comparing hash512sum.
|
||||||
|
* @throws InternalError
|
||||||
|
*/
|
||||||
|
protected void purgeList() throws InternalError {
|
||||||
|
List<HostItem> blackList = new ArrayList<HostItem>();
|
||||||
|
boolean first = false;
|
||||||
|
byte[] hashsum;
|
||||||
|
for(HostItem host: hostList) {
|
||||||
|
// already have hashsum from 1st server
|
||||||
|
if (!first) {
|
||||||
|
first = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// ask hashsum
|
||||||
|
hashsum = getHashSum512(host);
|
||||||
|
if (!Arrays.equals(hash512, hashsum)) {
|
||||||
|
blackList.add(host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// purge list
|
||||||
|
for(HostItem host: blackList) {
|
||||||
|
hostList.remove(host);
|
||||||
|
}
|
||||||
|
writeLog("Host list purge: done", LogLevel.Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Reassemble file from file parts.
|
||||||
|
* Set success to true if file is reassembled successfully.
|
||||||
|
*/
|
||||||
|
protected void reassembleFile() {
|
||||||
|
boolean firstPart = true;
|
||||||
|
boolean abort = false;
|
||||||
|
long nextOffset = 0;
|
||||||
|
do {
|
||||||
|
if (firstPart) {
|
||||||
|
writeLog("Reassembling: First part", LogLevel.Info);
|
||||||
|
try {
|
||||||
|
// create file
|
||||||
|
Files.copy(new File(partsSubdir + filename + "_" + nextOffset + ".part").toPath(), new File(dirStorage + filename).toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
nextOffset = (new File(dirStorage + filename)).length();
|
||||||
|
firstPart = false;
|
||||||
|
} catch (IOException e) {
|
||||||
|
writeLog("Reassembling: aborting on first part", LogLevel.Warning);
|
||||||
|
abort = true;
|
||||||
|
}
|
||||||
|
} else if (nextOffset >= size) {
|
||||||
|
success = true;
|
||||||
|
writeLog("Reassembling: success", LogLevel.Info);
|
||||||
|
} else {
|
||||||
|
// append to file
|
||||||
|
try {
|
||||||
|
Files.write(new File(dirStorage + filename).toPath(), Files.readAllBytes(new File(partsSubdir + filename + "_" + nextOffset + ".part").toPath()), StandardOpenOption.APPEND);
|
||||||
|
nextOffset = (new File(dirStorage + filename)).length();
|
||||||
|
} catch (IOException e) {
|
||||||
|
abort = true;
|
||||||
|
writeLog("Aborting: bad number " + nextOffset, LogLevel.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while((!success) && (!abort));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Set size of file to download. Also download first file part.
|
||||||
|
* @throws InternalError
|
||||||
|
*/
|
||||||
|
protected void setSize() throws InternalError {
|
||||||
|
ProtocolP2PPacket<?> d = createProtocolP2PPacket(new LoadRequest(filename, 0, MAX_PARTIAL_SIZE));
|
||||||
|
try {
|
||||||
|
d.sendRequest(getHostItemSocket(hostList.get(0)));
|
||||||
|
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.");
|
||||||
|
writeLog("cannot get size.", LogLevel.Error);
|
||||||
|
throw new InternalError();
|
||||||
|
} else {
|
||||||
|
FilePart fp = (FilePart)p;
|
||||||
|
if (!fp.getFilename().equals(filename)) {
|
||||||
|
System.err.println("Error: wrong file received: `" + fp.getFilename() + "`");
|
||||||
|
writeLog("wrong file received: `" + fp.getFilename() + "`", LogLevel.Error);
|
||||||
|
throw new ProtocolError();
|
||||||
|
}
|
||||||
|
if (fp.getOffset() == 0) {
|
||||||
|
try {
|
||||||
|
Files.write(new File(partsSubdir + filename + "_0.part").toPath(), fp.getPartialContent());
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("Error: cannot write file (" + partsSubdir + filename + "_0.part)");
|
||||||
|
writeLog("cannot write file (" + partsSubdir + filename + "_0.part)", LogLevel.Error);
|
||||||
|
}
|
||||||
|
size = fp.getTotalSize();
|
||||||
|
if (fp.getPartialContent().length == size) {
|
||||||
|
stop = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.err.println("Error: wrong file part received.");
|
||||||
|
writeLog("wrong file part received.", LogLevel.Error);
|
||||||
|
throw new InternalError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (EmptyDirectory e) {
|
||||||
|
System.err.println("Error: empty directory.");
|
||||||
|
writeLog("empty directory.", LogLevel.Error);
|
||||||
|
throw new InternalError();
|
||||||
|
} catch (LocalException e) {
|
||||||
|
writeLog(e, LogLevel.Error);
|
||||||
|
throw new InternalError();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
writeLog(e, LogLevel.Error);
|
||||||
|
throw new InternalError();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
writeLog(e, LogLevel.Error);
|
||||||
|
throw new InternalError();
|
||||||
|
} catch (SocketClosed e){
|
||||||
|
System.err.println("setSize : SocketClosed");
|
||||||
|
writeLog("setSize : SocketClosed", LogLevel.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Close HostItem socket
|
||||||
|
* @param hostItem HostItem
|
||||||
|
*/
|
||||||
|
protected abstract void closeHostItemSocket(HostItem hostItem);
|
||||||
|
|
||||||
|
|
||||||
|
/** Runnable implementation
|
||||||
|
*/
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
init();
|
||||||
|
if (stop) {
|
||||||
|
writeLog("File is smaller than part max size.", LogLevel.Info);
|
||||||
|
closeHostItemSocket(hostList.get(0));
|
||||||
|
} else {
|
||||||
|
writeLog("File is bigger than part max size.", LogLevel.Info);
|
||||||
|
purgeList();
|
||||||
|
initThreads();
|
||||||
|
while(!stop) {
|
||||||
|
assignTasks();
|
||||||
|
checkTasksStatus();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeLog("Reassembling file parts.", LogLevel.Info);
|
||||||
|
reassembleFile();
|
||||||
|
} catch(InternalError e) {
|
||||||
|
writeLog("Error while downloading file. Aborting.", LogLevel.Error);
|
||||||
|
} finally {
|
||||||
|
stopTasks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Initialize infos about file to download (size, hash512sum, partslist to dl).
|
||||||
|
* Also download first partfile (to get size).
|
||||||
|
* @throws InternalError
|
||||||
|
*/
|
||||||
|
protected void init() throws InternalError {
|
||||||
|
// get size
|
||||||
|
setSize();
|
||||||
|
|
||||||
|
// get hashsum from 1st server in list
|
||||||
|
hash512 = getHashSum512(hostList.get(0));
|
||||||
|
if (hash512.length == 0) {
|
||||||
|
writeLog("no hash512sum support.", LogLevel.Error);
|
||||||
|
throw new InternalError();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add tasks
|
||||||
|
if (!stop) {
|
||||||
|
for(long i=MAX_PARTIAL_SIZE; i<size;i+=MAX_PARTIAL_SIZE) {
|
||||||
|
offsetsToAsk.add(Long.valueOf(i));
|
||||||
|
}
|
||||||
|
writeLog("Adding tasks: done", LogLevel.Info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Getter for HostItem socket
|
||||||
|
* @param hostItem HostItem
|
||||||
|
*/
|
||||||
|
protected abstract Object getHostItemSocket(HostItem hostItem);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,310 @@
|
|||||||
|
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<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;
|
||||||
|
|
||||||
|
/** 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<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.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue