Merge branch 'WIP_etape1' of flavien/Projet_JAVA_P2P_STRI2A into master

Fin de l’étape 1
This commit is contained in:
Louis Royer 2020-01-25 22:45:23 +01:00 committed by Gitea
commit 3fc24dfef6
27 changed files with 1609 additions and 1 deletions

104
.gitignore vendored
View File

@ -1,4 +1,108 @@
.DS_Store
*.swp
*.class
bin
project.geany
P2P_JAVA_PROJECT_SERVER
P2P_JAVA_PROJECT_CLIENT
.classpath
.project
# Created by https://www.gitignore.io/api/java,eclipse
# Edit at https://www.gitignore.io/?templates=java,eclipse
### Eclipse ###
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# PyDev specific (Python IDE for Eclipse)
*.pydevproject
# CDT-specific (C/C++ Development Tooling)
.cproject
# CDT- autotools
.autotools
# Java annotation processor (APT)
.factorypath
# PDT-specific (PHP Development Tools)
.buildpath
# sbteclipse plugin
.target
# Tern plugin
.tern-project
# TeXlipse plugin
.texlipse
# STS (Spring Tool Suite)
.springBeans
# Code Recommenders
.recommenders/
# Annotation Processing
.apt_generated/
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet
### Eclipse Patch ###
# Eclipse Core
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath
# Annotation Processing
.apt_generated
.sts4-cache/
### Java ###
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# End of https://www.gitignore.io/api/java,eclipse

File diff suppressed because one or more lines are too long

82
doc/protocol.md Normal file
View File

@ -0,0 +1,82 @@
# P2P-JAVA-PROJECT version 1 (Protocol for step 1)
All messages begins with `P2P-JAVA-PROJECT VERSION 1.0\n` (this version of the protocol).
## Client messages
- `LIST\n`: ask the server to list files from server root directory
- `DOWNLOAD\n<FILENAME>\n`: ask the server to download file <FILENAME> from server root directory. Only one filename is allowed per request.
## Server responses
- The response to `LIST` request is in the format `LIST\n<FILENAME 1>\n<FILENAME 2>\n[…]<LAST FILENAME>\n\n`
- The response to `DOWNLOAD` request is `LOAD\n<FILESIZE (BYTES)>\n<POSITION>\n<CONTENT OF FILE>` or `NOT FOUND\n` if the file doesn't exists.
- The server send a `PROTOCOL ERROR\n` message if it doesn't understands what the client sent.
- The server send a `INTERNAL ERROR\n` message if it encounters an internal error.
# P2P-JAVA-PROJECT version 1.1 (Binary protocol for step 1)
All strings in the datagram are utf-8 encoded.
```Datagram format
1 byte: [0-7: VERSION(0x11, first quartet is major version, second is minor)]
1 byte: [8-15: REQUEST/RESPONSE CODE]
2 bytes: [16-31: RESERVED FOR FUTURE USE]
4 bytes: [32-63: PAYLOAD SIZE IN BYTES]
x bytes: [64-xx: PAYLOAD]
```
## Requests and responses codes
- REQUESTS (msb is 0):
- `LIST` (0x00)
- `LOAD` (0x01)
- RESPONSES (msb is 1):
- `LIST` (0x80)
- `LOAD` (0x81)
- `VERSION ERROR` (0xC0)
- `PROTOCOL ERROR` (0xC1)
- `INTERNAL ERROR` (0xC2)
- `EMPTY DIRECTORY` (0xC3)
- `NOT FOUND` (0xC4)
- `EMPTY FILE` (0xC5)
### List
Payload size for list request is always zero.
Payload for list response is filenames separated by `\n`. Payload size for list response is never zero.
#### Empty directory
When directory is empty.
Payload size for empty directory is always zero.
### Load
#### Not found
Response when the file requested is not found on the server.
Payload size for Not found is zero.
#### Load response
Payload contains
```
8 bytes: [64-127: OFFSET OF FILE CONTENT IN BYTES]
8 bytes: [128-191: TOTAL FILESIZE]
4 bytes: [192-223: FILENAME SIZE] (cannot be > to PAYLOAD_SIZE - 20 or be zero)
y bytes: [<FILENAME>]
z bytes: [FILE CONTENT]
```
#### Load request
Payload contains only the name of the file to load.
### Other response code (errors)
#### Version error
Response when datagram received use wrong version code.
#### Protocol error
Response when the request cannot be interpreted (but version is correct).
Payload size for Protocol error is zero
#### Internal error
Response in internal failure case.
Payload size for Internal error is zero.

View File

@ -0,0 +1,179 @@
package clientP2P;
import exception.InternalError;
import exception.ProtocolError;
import exception.SizeError;
import exception.TransmissionError;
import exception.VersionError;
import remoteException.EmptyFile;
import remoteException.EmptyDirectory;
import remoteException.InternalRemoteError;
import remoteException.NotFound;
import remoteException.ProtocolRemoteError;
import remoteException.VersionRemoteError;
import java.net.UnknownHostException;
import java.util.Scanner;
import java.net.InetAddress;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.io.IOException;
import java.nio.file.Files;
import java.io.File;
import protocolP2P.ProtocolP2PDatagram;
import protocolP2P.Payload;
import protocolP2P.RequestResponseCode;
import protocolP2P.FileList;
import protocolP2P.FilePart;
import protocolP2P.LoadRequest;
/** Implementation of P2P-JAVA-PROJECT CLIENT
* @author Louis Royer
* @author Flavien Haas
* @author JS Auge
* @version 1.0
*/
public class ClientManagementUDP implements Runnable {
private String baseDirectory;
private int UDPPort;
private String host;
private DatagramSocket socket;
/** Constructor for UDP implementation, with baseDirectory and UDPPort parameters.
* @param baseDirectory the root directory where files are stored
* @param host hostname of the server
* @param UDPPort the server will listen on this port
*/
public ClientManagementUDP(String baseDirectory, String host, int UDPPort) {
this.baseDirectory = baseDirectory;
this.host = host;
this.UDPPort = UDPPort;
try {
socket = new DatagramSocket();
} catch (SocketException e) {
System.err.println("Error: No socket available.");
System.exit(-1);
}
}
/** Implementation of Runnable
*/
public void run() {
try {
String[] list = listDirectory();
System.out.println("Files present on the server:");
for(String listItem: list) {
System.out.println(listItem);
}
System.out.println("Name of the file to download:");
Scanner scanner = new Scanner(System.in);
String f = scanner.nextLine();
download(f);
System.out.println("File sucessfully downloaded");
} catch (EmptyDirectory e) {
System.err.println("Error: Server has no file in directory");
} catch (InternalError e) {
System.err.println("Error: Client internal error");
} catch (UnknownHostException e) {
System.err.println("Error: Server host is unknown");
} catch (IOException e) {
System.err.println("Error: Request cannot be send or response cannot be received");
} catch (TransmissionError e) {
System.err.println("Error: Message received is too big");
} catch (ProtocolError e) {
System.err.println("Error: Cannot decode servers response");
} catch (VersionError e) {
System.err.println("Error: Servers response use bad version of the protocol");
} catch (SizeError e) {
System.err.println("Error: Cannot handle this packets because of internal representation limitations of numbers on the client");
} catch (InternalRemoteError e) {
System.err.println("Error: Server internal error");
} catch (ProtocolRemoteError e) {
System.err.println("Error: Server cannot decode clients request");
} catch (VersionRemoteError e) {
System.err.println("Error: Server cannot decode this version of the protocol");
} catch (NotFound e) {
System.err.println("Error: Server has not this file in directory");
} catch (EmptyFile e) {
System.err.println("Error: File is empty");
}
}
/** Try to download a file
* @param filename name of the file to download
* @throws NotFound
* @throws InternalError
* @throws UnknownHostException
* @throws IOException
* @throws TransmissionError
* @throws ProtocolError
* @throws VersionError
* @throws SizeError
* @throws InternalRemoteError
* @throws ProtocolRemoteError
* @throws VersionRemoteError
* @throws EmptyFile
*/
private void download(String filename) throws EmptyFile, NotFound, InternalError, UnknownHostException, IOException, TransmissionError, ProtocolError, VersionError, SizeError, InternalRemoteError, ProtocolRemoteError, VersionRemoteError {
ProtocolP2PDatagram d = new ProtocolP2PDatagram((Payload) new LoadRequest(filename));
d.send(socket, host, UDPPort);
try {
Payload p = ProtocolP2PDatagram.receive(socket).getPayload();
assert p instanceof FilePart : "This payload must be instance of FilePart";
if (!(p instanceof FilePart)) {
throw new InternalError();
} else {
FilePart fp = (FilePart)p;
if (!fp.getFilename().equals(filename)) {
System.err.println("Error: wrong file received");
throw new ProtocolError();
}
if (fp.getOffset() != 0 || fp.getPartialContent().length != fp.getTotalSize()) {
System.err.println("offset: " + fp.getOffset() + " ; content.length: " + fp.getPartialContent().length + " ; totalSize: " + fp.getTotalSize());
System.err.println("Error: cannot handle partial files (not implemented)");
throw new InternalError();
}
try {
Files.write(new File(baseDirectory + filename).toPath(), fp.getPartialContent());
} catch (IOException e) {
System.err.println("Error: cannot write file (" + baseDirectory + filename + ")");
}
}
} catch (EmptyDirectory e) {
throw new ProtocolError();
}
}
/** list servers directory content
* @return list of files
* @throws InternalError
* @throws UnknowHostException
* @throws IOException
* @throws TransmissionError
* @throws ProtocolError
* @throws VersionError
* @throws SizeError
* @throws EmptyDirectory
* @throws InternalRemoteError
* @throws ProtocolRemoteError
* @throws VersionRemoteError
*/
private String[] listDirectory() throws EmptyDirectory, InternalError, UnknownHostException, IOException, TransmissionError, ProtocolError, VersionError, SizeError, InternalRemoteError, ProtocolRemoteError, VersionRemoteError {
ProtocolP2PDatagram d = new ProtocolP2PDatagram(new Payload(RequestResponseCode.LIST_REQUEST));
d.send(socket, host, UDPPort);
try {
Payload p = ProtocolP2PDatagram.receive(socket).getPayload();
assert p instanceof FileList : "This payload must be instance of Filelist";
if (!(p instanceof FileList)) {
throw new InternalError();
} else {
return ((FileList)p).getFileList();
}
} catch (NotFound e) {
throw new ProtocolError();
} catch (EmptyFile e) {
throw new ProtocolError();
}
}
}

View File

@ -0,0 +1,25 @@
package clientP2P;
import clientP2P.ClientManagementUDP;
import tools.Directories;
public class ClientP2P {
private String host;
private int port;
private Directories directories;
public ClientP2P() {
directories = new Directories("P2P_JAVA_PROJECT_CLIENT");
host = "localhost";
port = 40001;
System.out.println("Client will try to contact server at " + host + " on port " + port + ". It will save files in " + directories.getDataHomeDirectory());
directories.askOpenDataHomeDirectory();
}
public static void main(String [] args) {
ClientP2P c = new ClientP2P();
ClientManagementUDP cm = new ClientManagementUDP(c.directories.getDataHomeDirectory(), c.host, c.port);
Thread t = new Thread(cm);
t.setName("client P2P-JAVA-PROJECT");
t.start();
}
}

View File

@ -0,0 +1,4 @@
package exception;
public class InternalError extends Exception {
private static final long serialVersionUID = 11L;
}

View File

@ -0,0 +1,4 @@
package exception;
public class ProtocolError extends Exception {
private static final long serialVersionUID = 11L;
}

View File

@ -0,0 +1,5 @@
package exception;
/** Used on reception side when size as set in datagram is too big, and we cant store this in a int/long as usual. */
public class SizeError extends Exception {
private static final long serialVersionUID = 11L;
}

View File

@ -0,0 +1,4 @@
package exception;
public class TransmissionError extends Exception {
private static final long serialVersionUID = 11L;
}

View File

@ -0,0 +1,4 @@
package exception;
public class VersionError extends Exception {
private static final long serialVersionUID = 11L;
}

View File

@ -0,0 +1,13 @@
package protocolP2P;
/** Request/Response code's type enum.
* @author Louis Royer
* @author Flavien Haas
* @author JS Auge
* @version 1.0
*/
public enum CodeType {
REQUEST,
RESPONSE,
ERROR
}

View File

@ -0,0 +1,109 @@
package protocolP2P;
import java.util.Arrays;
import protocolP2P.Payload;
import protocolP2P.RequestResponseCode;
import exception.TransmissionError;
import exception.ProtocolError;
import exception.InternalError;
import exception.SizeError;
import java.io.UnsupportedEncodingException;
/** Representation of payload for list response.
* @author Louis Royer
* @author Flavien Haas
* @author JS Auge
* @version 1.0
*/
public class FileList extends Payload {
private String[] fileList;
/** Constructor (typically used by the server) with an ArrayList parameter containing
* filenames.
* @param fileList a list of files. Must not be empty.
* @throws InternalError
*/
public FileList(String[] fileList) throws InternalError {
super(RequestResponseCode.LIST_RESPONSE);
/* assert to help debugging */
assert fileList.length != 0 : "Payload size of FileList must not be empty, use EmptyDirectory from Payload instead";
if (fileList.length == 0) {
throw new InternalError();
}
this.fileList = fileList;
}
/** Constructor (typically used by client) with a byte[] parameter containing the datagram received.
* @param datagram the full datagram received
* @throws SizeError
* @throws InternalError
* @throws ProtocolError
* @throws TransmissionError
*/
protected FileList(byte[] datagram) throws TransmissionError, SizeError, ProtocolError, InternalError {
super(datagram);
/* assert to help debugging */
assert requestResponseCode == RequestResponseCode.LIST_RESPONSE : "FileList subclass is incompatible with this datagram, request/response code must be checked before using this constructor";
/* InternalErrorException */
if (requestResponseCode!= RequestResponseCode.LIST_RESPONSE) {
throw new InternalError();
}
int size = getPayloadSize(datagram);
try {
fileList = (new String(datagram, 8, size, "UTF-8")).split("\n");
} catch (UnsupportedEncodingException e) {
throw new InternalError();
}
}
/** Returns a byte[] containing datagram with padding.
* This datagram is still incomplete and should not be send directly.
* ProtocolP2PDatagram will use this method to generate the complete datagram.
* @return datagram with padding
* @throws InternalError
*/
protected byte[] toDatagram() throws InternalError {
// compute size
int size = 8;
for (String s : fileList) {
size += s.length();
size += 1;
}
size -=1;
byte[] datagram = new byte[size]; // java initialize all to zero
// set request/response code
datagram[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue;
// bits 16-31 are reserved for future use
setPayloadSize(size - 8, datagram);
// Write fileList
int bCount = 8;
for(String s : fileList) {
if (bCount != 8) { // not on first iteration
try {
datagram[bCount] = "\n".getBytes("UTF-8")[0]; // separator
} catch (UnsupportedEncodingException e) {
throw new InternalError();
}
bCount += 1;
}
// Copy filename
try {
byte[] sb = s.getBytes("UTF-8");
for(byte b : sb) {
datagram[bCount] = b;
bCount += 1;
}
} catch (UnsupportedEncodingException e) {
throw new InternalError();
}
}
return datagram;
}
/** fileList getter.
* @return fileList
*/
public String[] getFileList() {
return fileList;
}
}

View File

@ -0,0 +1,199 @@
package protocolP2P;
import protocolP2P.Payload;
import protocolP2P.RequestResponseCode;
import exception.ProtocolError;
import exception.InternalError;
import exception.SizeError;
import exception.TransmissionError;
import tools.BytesArrayTools;
import java.util.Arrays;
import java.io.UnsupportedEncodingException;
/** Representation of payload for load response.
* @author Louis Royer
* @author Flavien Haas
* @author JS Auge
* @version 1.0
*/
public class FilePart extends Payload {
private String filename;
private long totalSize;
private long offset;
private byte[] partialContent;
/** Constructor (typically used by server) with informations about file part to send as parameters.
* @param filename name of the file to send
* @param totalSize total size of the file to send
* @param offset where in the file begins the part we are sending
* @param partialContent content of the file we send
* @throws InternalError
*/
public FilePart(String filename, long totalSize, long offset, byte[] partialContent) throws InternalError {
super(RequestResponseCode.LOAD_RESPONSE);
/* asserts to help debugging */
assert totalSize >= 0 : "totalSize cannot be negative";
assert partialContent.length != 0 : "partialContent.length cannot be zero, see RRCode.EMPTY_FILE";
assert totalSize >= partialContent.length : "totalSize must be greater than partialContent.length";
assert offset >= 0 : "offset cannot be negative";
assert filename != null : "filename is required";
if (totalSize < 0 || partialContent.length == 0 || totalSize < partialContent.length
|| offset < 0 || filename == null) {
throw new InternalError();
}
this.filename = filename;
this.totalSize = totalSize;
this.offset = offset;
this.partialContent = partialContent;
}
/** Constructor (typically used by client) with datagram received as parameter.
* @param datagram the full datagram received
* @throws SizeError
* @throws InternalError
* @throws TransmissionError
*/
protected FilePart(byte[] datagram) throws TransmissionError, SizeError, ProtocolError, InternalError {
super(datagram);
/* assert to help debugging */
assert requestResponseCode == RequestResponseCode.LOAD_RESPONSE : "FilePart subclass is incompatible with this datagram, request/response code must be checked before using this constructor";
/* InternalErrorException */
if (requestResponseCode != RequestResponseCode.LOAD_RESPONSE) {
throw new InternalError();
}
setOffset(datagram); // this can throw SizeError
setTotalSize(datagram); // this can throw SizeError
setFilename(datagram); // this can throw ProtocolError, SizeError
setPartialContent(datagram); // this can throw SizeError
}
/** Returns a byte[] containing datagram with padding.
* This datagram is still incomplete and should not be send directly.
* ProtocolP2PDatagram will use this method to generate the complete datagram.
* @return datagram with padding
* @throws InternalError
*/
protected byte[] toDatagram() throws InternalError {
// compute payload size
int size = 28 + filename.length() + partialContent.length;
byte[] datagram = new byte[size]; // java initialize all to zero
// set request/response code
datagram[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue;
// bits 16-31 are reserved for future use
setPayloadSize(size - 8, datagram);
// write offset to datagram (Byte 8)
BytesArrayTools.write(datagram, 8, offset);
// write totalSize to datagram (Byte 16)
BytesArrayTools.write(datagram, 16, totalSize);
// write filenames size to datagram
BytesArrayTools.write(datagram, 24, filename.length());
// write filename to datagram
try {
byte[] bFilename = filename.getBytes("UTF-8");
int i = filename.length() + 24;
for (byte b : bFilename) {
datagram[i] = b;
i += 1;
}
// write partialContent to datagram
for (byte b: partialContent) {
datagram[i] = b;
i += 1;
}
return datagram;
} catch (UnsupportedEncodingException e) {
throw new InternalError();
}
}
/** Write from bytes 8 to 15 of datagram into offset.
* @param datagram received datagram
* @throws SizeError
*/
private void setOffset(byte[] datagram) throws SizeError {
offset = BytesArrayTools.readLong(datagram, 8);
}
/** Write from bytes 16 to 23 of datagram into totalSize.
* @param datagram received datagram
* @throws SizeError
*/
private void setTotalSize(byte[] datagram) throws SizeError {
totalSize = BytesArrayTools.readLong(datagram, 16);
}
/** Read filenames size from bytes 24 to 27 of datagram.
* @param datagram received datagram
* @throws ProtocolError
* @throws SizeError
* @return filenames size
*/
private int getFilenameSize(byte[] datagram) throws SizeError, ProtocolError {
int size = BytesArrayTools.readInt(datagram, 24); // this can throw SizeError
// filename size cannot be zero
if (size == 0) {
throw new ProtocolError();
}
// offset (8B) + totalSize (8B) + filenameSize (4B) = 20B
if ((20 + size) > getPayloadSize(datagram)) {
throw new ProtocolError();
}
return size;
}
/** Write filename from byte 28 to byte (28 + (filenameSize - 1)) of datagram.
* @param datagram received datagram
* @throws ProtocolError
* @throws SizeError
* @throws InternalError
*/
private void setFilename(byte[] datagram) throws ProtocolError, SizeError, InternalError {
int filenameSize = getFilenameSize(datagram); // this can throw ProtocolError or SizeError
try {
filename = new String(datagram, 28, filenameSize, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new InternalError();
}
}
/** Write partialContent from byte (28 + filenameSize) to byte (8 + payloadSize) of datagram.
* @param datagram received datagram
* @throws SizeError
* @throws ProtocolError
*/
private void setPartialContent(byte[] datagram) throws ProtocolError, SizeError {
int start = 28 + getFilenameSize(datagram); // this can throw SizeError or ProtocolError
int end = 8 + getPayloadSize(datagram); // this can throw SizeError
try {
partialContent = Arrays.copyOfRange(datagram, start, end);
} catch (ArrayIndexOutOfBoundsException e) {
throw new ProtocolError();
}
}
/** partialContent getter.
* @return partialcontent
*/
public byte[] getPartialContent() {
return partialContent;
}
/** filename getter.
* @return String
*/
public String getFilename() {
return filename;
}
/** offset getter.
* @return offset
*/
public long getOffset() {
return offset;
}
/** totalSize getter.
* @return totalSize
*/
public long getTotalSize() {
return totalSize;
}
}

View File

@ -0,0 +1,90 @@
package protocolP2P;
import protocolP2P.Payload;
import protocolP2P.RequestResponseCode;
import exception.TransmissionError;
import exception.ProtocolError;
import exception.InternalError;
import exception.SizeError;
import java.io.UnsupportedEncodingException;
/** Representation of payload for load request.
* @author Louis Royer
* @author Flavien Haas
* @author JS Auge
* @version 1.0
*/
public class LoadRequest extends Payload {
private String filename;
/** Constructor (typically used by the server) with a filename parameter.
* @param filename name of the file to download. Must not be empty.
* @throws InternalError
*/
public LoadRequest(String filename) throws InternalError {
super(RequestResponseCode.LOAD_REQUEST);
/* assert to help debugging */
assert filename.length() != 0 : "Payload size of LoadRequest must not be empty";
if (filename.length() == 0) {
throw new InternalError();
}
this.filename = filename;
}
/** Constructor (typically used by client) with a byte[] parameter containing the datagram received.
* @param datagram the full datagram received
* @throws SizeError
* @throws InternalError
* @throws ProtocolError
* @throws TransmissionError
*/
protected LoadRequest(byte[] datagram) throws TransmissionError, SizeError, ProtocolError, InternalError {
super(datagram);
/* assert to help debugging */
assert requestResponseCode == RequestResponseCode.LOAD_REQUEST : "LoadRequest subclass is incompatible with this datagram, request/response code must be checked before using this constructor";
/* InternalErrorException */
if (requestResponseCode!= RequestResponseCode.LOAD_REQUEST) {
throw new InternalError();
}
int size = getPayloadSize(datagram);
try {
filename = new String(datagram, 8, size, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new InternalError();
}
}
/** Returns a byte[] containing datagram with padding.
* This datagram is still incomplete and should not be send directly.
* ProtocolP2PDatagram will use this method to generate the complete datagram.
* @return datagram with padding
* @throws InternalError
*/
protected byte[] toDatagram() throws InternalError {
// compute size
int size = 8 + filename.length();
byte[] datagram = new byte[size]; // java initialize all to zero
// set request/response code
datagram[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue;
// bits 16-31 are reserved for future use
setPayloadSize(size - 8, datagram);
// Write filename
int bCount = 8;
try {
byte[] sb = filename.getBytes("UTF-8");
for(byte b : sb) {
datagram[bCount] = b;
bCount += 1;
}
} catch (UnsupportedEncodingException e) {
throw new InternalError();
}
return datagram;
}
/** filename getter.
* @return filename
*/
public String getFilename() {
return filename;
}
}

View File

@ -0,0 +1,116 @@
package protocolP2P;
import protocolP2P.RequestResponseCode;
import protocolP2P.FilePart;
import protocolP2P.FileList;
import protocolP2P.LoadRequest;
import exception.ProtocolError;
import exception.InternalError;
import exception.TransmissionError;
import exception.SizeError;
import tools.BytesArrayTools;
/** Representation of payload. If payload has a size, use subclasses instead.
* @author Louis Royer
* @author Flavien Haas
* @author JS Auge
* @version 1.0
*/
public class Payload {
protected RequestResponseCode requestResponseCode;
protected final static int PAYLOAD_SIZE_POSITION = 4;
protected final static int PAYLOAD_START_POSITION = 8;
/** Consructor used to create Payload with a payload size of zero using a RRCode.
* @param requestResponseCode Request/Response code associated with the payload
* @throws InternalError
*/
public Payload(RequestResponseCode requestResponseCode) throws InternalError {
/* asserts to help debugging */
assert requestResponseCode != RequestResponseCode.LIST_RESPONSE || (this instanceof FileList) : "LIST_RESPONSE must use FilePart class";
assert requestResponseCode != RequestResponseCode.LOAD_RESPONSE || (this instanceof FilePart) : "LOAD_RESPONSE must use FileList class";
assert requestResponseCode != RequestResponseCode.LOAD_REQUEST || (this instanceof LoadRequest) : "LOAD_REQUEST must use LoadRequest class";
this.requestResponseCode = requestResponseCode;
checkRequestResponseCode(); // this can throw InternalError
}
/** Constructor used to create a Payload (when no more specific subclasses exists) using datagram as parameter.
* If payload size is not empty, using subclass is required.
* @param datagram the full datagram received
* @throws ProtocolError
* @throws InternalError
* @throws TransmissionError
* @throws SizeError
*/
protected Payload(byte[] datagram) throws SizeError, ProtocolError, InternalError, TransmissionError {
/* asserts to help debugging */
assert getPayloadSize(datagram) + 8 <= datagram.length : "Payload is truncated";
if (datagram.length < getPayloadSize(datagram) + 8) {
throw new TransmissionError();
}
assert RequestResponseCode.fromCode(datagram[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.LIST_RESPONSE || (this instanceof FileList) : "LIST_RESPONSE must use FilePart class";
assert RequestResponseCode.fromCode(datagram[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.LOAD_RESPONSE || (this instanceof FilePart) : "LOAD_RESPONSE must use FileList class";
assert RequestResponseCode.fromCode(datagram[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.LOAD_REQUEST || (this instanceof LoadRequest) : "LOAD_REQUEST must use LoadRequest class";
requestResponseCode = RequestResponseCode.fromCode(datagram[RequestResponseCode.RRCODE_POSITION]);
checkRequestResponseCode(); // this can throw InternalError
}
/** Used to check RRCode used is compatible with this class use, or if a more specific subclass is required.
* @throws InternalError
*/
private void checkRequestResponseCode() throws InternalError {
/* Incorrect use cases (use subclasses instead) */
if ((requestResponseCode == RequestResponseCode.LIST_RESPONSE && !(this instanceof FileList))
|| (requestResponseCode == RequestResponseCode.LOAD_RESPONSE && !(this instanceof FilePart))
|| (requestResponseCode == RequestResponseCode.LOAD_REQUEST && !(this instanceof LoadRequest))) {
throw new InternalError();
}
}
/** Returns a byte[] containing datagram with padding.
* This datagram is still incomplete and should not be send directly.
* ProtocolP2PDatagram will use this method to generate the complete datagram.
* @return datagram with padding
* @throws InternalError
*/
protected byte[] toDatagram() throws InternalError {
// InternalError is impossible in this method on Payload class but still on subclasses
byte [] datagram = new byte[8]; // java initialize all to zero
// size is zero (and this is the default)
// set request/response code
datagram[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue;
// bits 16-31 are reserved for future use
// payload size is 0 (this is what java have initialized datagram)
return datagram;
}
/** Set payloads size in a datagram.
* @param size integer representing payload size
* @param datagram datagram to be completed
* @throws InternalError
*/
protected static void setPayloadSize(int size, byte[] datagram) throws InternalError {
/* assert to help debugging */
assert size >= 0: "Payload size cannot be negative";
if (size < 0) {
// We don't throw SizeError
// because this is only for reception side
throw new InternalError();
}
BytesArrayTools.write(datagram, PAYLOAD_SIZE_POSITION, size);
}
/** Get payloads size from a datagram.
* @param datagram the full datagram received
* @return integer representing payload size
* @throws SizeError
*/
protected static int getPayloadSize(byte[] datagram) throws SizeError {
return BytesArrayTools.readInt(datagram, PAYLOAD_SIZE_POSITION);
}
/** RRCode getter.
* @return Request/Response code
*/
public RequestResponseCode getRequestResponseCode() {
return requestResponseCode;
}
}

View File

@ -0,0 +1,243 @@
package protocolP2P;
import exception.InternalError;
import exception.ProtocolError;
import exception.SizeError;
import exception.TransmissionError;
import exception.VersionError;
import remoteException.EmptyDirectory;
import remoteException.InternalRemoteError;
import remoteException.NotFound;
import remoteException.ProtocolRemoteError;
import remoteException.VersionRemoteError;
import remoteException.EmptyFile;
import protocolP2P.Payload;
import protocolP2P.RequestResponseCode;
import protocolP2P.LoadRequest;
import protocolP2P.FileList;
import protocolP2P.FilePart;
import java.util.ArrayList;
import java.lang.Byte;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.io.IOException;
import java.net.UnknownHostException;
/** Representation of datagram.
* @author Louis Royer
* @author Flavien Haas
* @author JS Auge
* @version 1.0
*/
public class ProtocolP2PDatagram {
private final static byte PROTOCOL_VERSION = 0x11;
private final static int VERSION_POSITON = 0;
private byte version;
private Payload payload;
private InetAddress hostR;
private int portR;
/** Constructor with payload parameter (typically used when sending datagram).
* @param payload the payload associated with the datagram to send
*/
public ProtocolP2PDatagram(Payload payload) {
version = PROTOCOL_VERSION;
this.payload = payload;
}
/** Send datagram on socket (from client)
* @param socket DatagramSocket used to send datagram.
* @param host host to send datagram (null if this is a response)
* @param port port to send datagram (null if this is a response)
* @throws InternalError
* @throws UnknownHostException
* @throws IOException
*/
public void send(DatagramSocket socket, String host, int port) throws InternalError, UnknownHostException, IOException {
InetAddress dst = InetAddress.getByName(host);
byte[] datagram = toDatagram();
// generate DatagramPacket
DatagramPacket datagramPacket = new DatagramPacket(datagram, datagram.length, dst, port);
// send it
socket.send(datagramPacket);
}
/** Send datagram on socket (from server, as a response)
* @param socket DatagramSocket used to send datagram.
* @param received datagram to respond (aka request)
* @throws InternalError
* @throws IOException
*/
public void send(DatagramSocket socket, ProtocolP2PDatagram received) throws InternalError, IOException {
assert received.getPortR() != 0 && received.getHostR() != null : "This method should be used only as response to a request";
if (received.getPortR() == 0 || received.getHostR() == null) {
throw new InternalError();
}
byte[] datagram = toDatagram();
// generate DatagramPacket
DatagramPacket datagramPacket = new DatagramPacket(datagram, datagram.length, received.getHostR(), received.getPortR());
socket.send(datagramPacket);
}
/** Send a response.
* @param socket DatagramSocket used to send response
* @param host host to send response
* @param port port to send response
* @throws InternalError
* @throws IOException
*/
protected void sendResponse(DatagramSocket socket, InetAddress host, int port) throws InternalError, IOException {
assert port != 0 && host != null : "This method should be used only as response to a request";
if (port == 0 || host == null) {
throw new InternalError();
}
byte[] datagram = toDatagram();
DatagramPacket datagramPacket = new DatagramPacket(datagram, datagram.length, host, port);
socket.send(datagramPacket);
}
/** Receive datagram on socket
* @param socket DatagramSocket used to receive datagram
* @throws TransmissionError
* @throws ProtocolError
* @throws VersionError
* @throws InternalError
* @throws SizeError
* @throws ProtocolRemoteError
* @throws VersionRemoteError
* @throws InternalRemoteError
* @throws EmptyDirectory
* @throws NotFound
* @throws IOException
* @throws EmptyFile
*/
public static ProtocolP2PDatagram receive(DatagramSocket socket) throws EmptyFile, NotFound, EmptyDirectory, InternalRemoteError, VersionRemoteError, ProtocolRemoteError, TransmissionError, ProtocolError, VersionError, InternalError, SizeError, IOException {
// reception
byte[] datagram = new byte[4096];
DatagramPacket reception = new DatagramPacket(datagram, datagram.length);
socket.receive(reception);
// contruction
try {
ProtocolP2PDatagram p = new ProtocolP2PDatagram(datagram);
p.setHostR(reception.getAddress());
p.setPortR(reception.getPort());
Payload payload = p.getPayload();
switch (payload.getRequestResponseCode()) {
case PROTOCOL_ERROR :
throw new ProtocolRemoteError();
case VERSION_ERROR :
throw new VersionRemoteError();
case INTERNAL_ERROR :
throw new InternalRemoteError();
case EMPTY_DIRECTORY :
throw new EmptyDirectory();
case NOT_FOUND :
throw new NotFound();
case EMPTY_FILE:
throw new EmptyFile();
default :
return p;
}
} catch (TransmissionError e) {
(new ProtocolP2PDatagram(new Payload(RequestResponseCode.INTERNAL_ERROR))).sendResponse(socket, reception.getAddress(), reception.getPort());
throw e;
} catch (ProtocolError e) {
(new ProtocolP2PDatagram(new Payload(RequestResponseCode.PROTOCOL_ERROR))).sendResponse(socket, reception.getAddress(), reception.getPort());
throw e;
} catch (VersionError e) {
(new ProtocolP2PDatagram(new Payload(RequestResponseCode.VERSION_ERROR))).sendResponse(socket, reception.getAddress(), reception.getPort());
throw e;
} catch (InternalError e) {
(new ProtocolP2PDatagram(new Payload(RequestResponseCode.INTERNAL_ERROR))).sendResponse(socket, reception.getAddress(), reception.getPort());
throw e;
} catch (SizeError e) {
(new ProtocolP2PDatagram(new Payload(RequestResponseCode.INTERNAL_ERROR))).sendResponse(socket, reception.getAddress(), reception.getPort());
throw e;
}
}
/** Private constructor with datagram as byte[] parameter (typically used when receiving datagram).
* @param datagram the full datagram received
* @throws TransmissionError
* @throws ProtocolError
* @throws VersionError
* @throws InternalError
* @throws SizeError
*/
private ProtocolP2PDatagram(byte[] datagram) throws TransmissionError, ProtocolError, VersionError, InternalError, SizeError {
// unwrap version
version = datagram[VERSION_POSITON];
checkProtocolVersion(); // this can throw VersionError
RequestResponseCode r = RequestResponseCode.fromCode(datagram[RequestResponseCode.RRCODE_POSITION]); // this can throw ProtocolError
switch (r) {
case LIST_RESPONSE:
payload = (Payload) new FileList(datagram);
break;
case LOAD_RESPONSE:
payload = (Payload) new FilePart(datagram);
break;
case LOAD_REQUEST:
payload = (Payload) new LoadRequest(datagram);
break;
default:
payload = new Payload(datagram); // this can throw TransmissionError
break;
}
}
/** Returns a byte[] containing full datagram (typically used when sending datagram).
* This datagram is still complete and ready to be send.
* @return the full datagram to send
* @throws InternalError
*/
protected byte[] toDatagram() throws InternalError {
byte[] datagram = payload.toDatagram();
datagram[VERSION_POSITON] = version;
return datagram;
}
/** Returns Payload associated with the datagram.
* @return payload associated with the datagram
*/
public Payload getPayload() {
return payload;
}
/** Used to check protocol version when a datagram is constructed from bytes[].
* @throws VersionError
*/
private void checkProtocolVersion() throws VersionError {
if (PROTOCOL_VERSION != version) {
throw new VersionError();
}
}
/** portR getter.
* @return portR
*/
protected int getPortR() {
return portR;
}
/** portR setter.
* @param portR portR
*/
protected void setPortR(int portR) {
this.portR = portR;
}
/** hostR getter.
* @return hostR
*/
protected InetAddress getHostR() {
return hostR;
}
/** hostH setter.
* @param hostR hostR
*/
protected void setHostR(InetAddress hostR) {
this.hostR = hostR;
}
}

View File

@ -0,0 +1,65 @@
package protocolP2P;
import protocolP2P.CodeType;
import exception.ProtocolError;
import java.util.HashMap;
import java.util.Map;
import java.lang.Byte;
/** Request/Response code enum.
* @author Louis Royer
* @author Flavien Haas
* @author JS Auge
* @version 1.0
*/
public enum RequestResponseCode {
LIST_REQUEST(CodeType.REQUEST, (byte)0x00),
LOAD_REQUEST(CodeType.REQUEST, (byte)0x01),
LIST_RESPONSE(CodeType.RESPONSE, (byte)0x80),
LOAD_RESPONSE(CodeType.RESPONSE, (byte)0x81),
VERSION_ERROR(CodeType.ERROR, (byte)0xC0),
PROTOCOL_ERROR(CodeType.ERROR, (byte)0xC1),
INTERNAL_ERROR(CodeType.ERROR, (byte)0xC2),
EMPTY_DIRECTORY(CodeType.ERROR, (byte)0xC3),
NOT_FOUND(CodeType.ERROR, (byte)0xC4),
EMPTY_FILE(CodeType.ERROR, (byte)0xC5);
public final CodeType codeType;
public final byte codeValue;
protected final static int RRCODE_POSITION = 1;
/* To be able to convert code to enum */
private static final Map<Byte, RequestResponseCode> BY_CODE = new HashMap<>();
/* Initialization of HashMap */
static {
for (RequestResponseCode r: values()) {
assert !BY_CODE.containsKey(Byte.valueOf(r.codeValue)) : "Duplicate in " + RequestResponseCode.class.getCanonicalName();
BY_CODE.put(Byte.valueOf(r.codeValue), r);
}
}
/** Private constructor
* @param codeType type of code (request or response)
* @param codeValue value of the element in datagram
* @return enum element
*/
private RequestResponseCode(CodeType codeType, byte codeValue) {
this.codeType = codeType;
this.codeValue = codeValue;
}
/** Gives enum from datagram code.
* @param code value of the element in datagram
* @return enum element
*/
protected static RequestResponseCode fromCode(byte code) throws ProtocolError {
RequestResponseCode r= BY_CODE.get(Byte.valueOf(code));
if (r == null) {
throw new ProtocolError();
}
return r;
}
}

View File

@ -0,0 +1,4 @@
package remoteException;
public class EmptyDirectory extends Exception {
private static final long serialVersionUID = 11L;
}

View File

@ -0,0 +1,4 @@
package remoteException;
public class EmptyFile extends Exception {
private static final long serialVersionUID = 11L;
}

View File

@ -0,0 +1,4 @@
package remoteException;
public class InternalRemoteError extends Exception {
private static final long serialVersionUID = 11L;
}

View File

@ -0,0 +1,4 @@
package remoteException;
public class NotFound extends Exception {
private static final long serialVersionUID = 11L;
}

View File

@ -0,0 +1,4 @@
package remoteException;
public class ProtocolRemoteError extends Exception {
private static final long serialVersionUID = 11L;
}

View File

@ -0,0 +1,4 @@
package remoteException;
public class VersionRemoteError extends Exception {
private static final long serialVersionUID = 11L;
}

View File

@ -0,0 +1,160 @@
package serverP2P;
import java.util.Vector;
import java.io.File;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.nio.file.Paths;
import java.nio.file.Files;
import protocolP2P.ProtocolP2PDatagram;
import protocolP2P.RequestResponseCode;
import protocolP2P.Payload;
import protocolP2P.LoadRequest;
import protocolP2P.FileList;
import protocolP2P.FilePart;
import exception.InternalError;
import exception.ProtocolError;
import exception.SizeError;
import exception.TransmissionError;
import exception.VersionError;
import remoteException.EmptyDirectory;
import remoteException.InternalRemoteError;
import remoteException.NotFound;
import remoteException.ProtocolRemoteError;
import remoteException.VersionRemoteError;
import remoteException.EmptyFile;
import java.util.Arrays;
/** Implementation of P2P-JAVA-PROJECT VERSION 1.0 protocol for UDP.
* @author Louis Royer
* @author Flavien Haas
* @author JS Auge
* @version 1.0
*/
public class ServerManagementUDP implements Runnable {
private String[] fileList;
private String baseDirectory;
private int UDPPort;
private DatagramSocket socket;
/** Constructor for UDP implementation, with baseDirectory and UDPPort parameters.
* @param baseDirectory the root directory where files are stored
* @param UDPPort the server will listen on this port
*/
public ServerManagementUDP(String baseDirectory, int UDPPort) {
this.baseDirectory = baseDirectory;
this.UDPPort = UDPPort;
initFileList();
try {
socket = new DatagramSocket(UDPPort);
} catch (SocketException e) {
System.err.println("Error: cannot listen on port " + UDPPort);
System.exit(-1);
}
}
/** Implementation of runnable. This methods allows to run the server.
*/
public void run() {
while(true) {
try {
ProtocolP2PDatagram pd = ProtocolP2PDatagram.receive(socket);
Payload p = pd.getPayload();
switch (p.getRequestResponseCode()) {
case LOAD_REQUEST:
System.out.println("Received LOAD_REQUEST");
assert p instanceof LoadRequest : "payload must be an instance of LoadRequest";
if (!(p instanceof LoadRequest)) {
sendInternalError(pd);
} else {
String filename = ((LoadRequest)p).getFilename();
try {
byte[] load = Files.readAllBytes(Paths.get(baseDirectory + filename));
if (Arrays.binarySearch(fileList, filename) >= 0) {
try {
if (load.length == 0) {
(new ProtocolP2PDatagram(new Payload(RequestResponseCode.EMPTY_FILE))).send(socket, pd);
} else {
(new ProtocolP2PDatagram((Payload)(new FilePart(filename, load.length, 0, load)))).send(socket, pd);
}
} catch (Exception e2) {
System.err.println(e2);
}
} else {
throw new IOException(); // to send a NOT_FOUND in the catch block
}
} catch (IOException e) {
try {
(new ProtocolP2PDatagram(new Payload(RequestResponseCode.NOT_FOUND))).send(socket, pd);
} catch (Exception e2) {
System.err.println(e2);
}
}
}
break;
case LIST_REQUEST:
System.out.println("Received LIST_REQUEST");
try {
if (fileList.length == 0) {
System.err.println("Sending EMPTY_DIRECTORY");
(new ProtocolP2PDatagram(new Payload(RequestResponseCode.EMPTY_DIRECTORY))).send(socket, pd);
} else {
System.out.println("Sending LIST_RESPONSE");
(new ProtocolP2PDatagram((Payload)(new FileList(fileList)))).send(socket, pd);
}
} catch (Exception e2) {
System.err.println(e2);
}
break;
default:
sendInternalError(pd);
}
} catch (NotFound e) {
} catch (EmptyDirectory e) {
} catch (InternalRemoteError e) {
} catch (VersionRemoteError e) {
} catch (ProtocolRemoteError e) {
} catch (IOException e) {
} catch (TransmissionError e) {
} catch (ProtocolError e) {
} catch (VersionError e) {
} catch (InternalError e) {
} catch (SizeError e) {
} catch (EmptyFile e) {
}
}
}
/** Initialize local list of all files allowed to be shared.
*/
private void initFileList() {
File folder = new File(baseDirectory);
Vector<String> v = new Vector<String>();
File[] files = folder.listFiles();
/* Add non-recursively files's names to fileList */
for (File f : files) {
if (f.isFile()) {
v.add(f.getName());
}
}
fileList = new String[v.size()];
v.toArray(fileList);
}
/** Send an internal error message.
* @param pd ProtocolP2PDatagram to respond
*/
private void sendInternalError(ProtocolP2PDatagram pd) {
try {
(new ProtocolP2PDatagram(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(socket, pd);
} catch (Exception e) {
System.err.println(e);
}
}
}

View File

@ -0,0 +1,23 @@
package serverP2P;
import serverP2P.ServerManagementUDP;
import tools.Directories;
public class ServerP2P {
private int port;
private Directories directories;
public ServerP2P() {
directories = new Directories("P2P_JAVA_PROJECT_SERVER");
port = 40001;
System.out.println("Server will listen on port " + port + " and serve files from " + directories.getDataHomeDirectory());
directories.askOpenDataHomeDirectory();
}
public static void main(String [] args) {
ServerP2P s = new ServerP2P();
ServerManagementUDP sm = new ServerManagementUDP(s.directories.getDataHomeDirectory(), s.port);
Thread t = new Thread(sm);
t.setName("server P2P-JAVA-PROJECT");
t.start();
}
}

View File

@ -0,0 +1,66 @@
package tools;
import exception.SizeError;
/** Helper to manipulate byte[].
* @author Louis Royer
* @author Flavien Haas
* @author JS Auge
* @version 1.0
*/
public class BytesArrayTools {
/** Write int in a bytearray
* @param array the array to write
* @param start where to begin writting
* @param value int to write
*/
public static void write(byte[] array, int start, int value) {
for(int i=0;i<4;i++) {
array[start + i] = (byte) ((value >> (8 * (3 - i))) & 0xFF);
}
}
/** Write long in a bytearray
* @param array the array to write
* @param start where to begin writting
* @param value long to write
*/
public static void write(byte[] array, int start, long value) {
for(int i=0;i<8;i++) {
array[start + i] = (byte) ((value >> (8 * (7 - i))) & 0xFF);
}
}
/** Read int from a bytearray
* @param array the array to read
* @param start where to begin reading
* @return value read as int
*/
public static int readInt(byte[] array, int start) throws SizeError {
int size = 0;
for(int i=0;i<4;i++) {
size |= ((int)array[start + i]) << (8* (3 -i));
}
if (size < 0) {
// Size in array is probably correct
// but we cannot store it into a int (or this will be negative)
throw new SizeError();
}
return size;
}
/** Read long from a bytearray
* @param array the array to read
* @param start where to begin reading
* @return value read as long
*/
public static long readLong(byte[] array, int start) throws SizeError {
long size = 0;
for(int i=0;i<8;i++) {
size |= ((int)array[start + i]) << (8* (7 - i));
}
if (size < 0) {
// Size in array is probably correct
// but we cannot store it into a int (or this will be negative)
throw new SizeError();
}
return size;
}
}

View File

@ -0,0 +1,89 @@
package tools;
import java.util.Scanner;
import java.io.File;
import java.lang.Runtime;
import java.io.IOException;
/** Helper to get application directories.
* @author Louis Royer
* @author Flavien Haas
* @author JS Auge
* @version 1.0
*/
public class Directories {
private String projectName;
private String dataHomeDirectory;
private String os;
/** Constructor with projectName parameter.
* @param projectName name of the project
*/
public Directories(String projectName) {
this.projectName = projectName;
os = System.getProperty("os.name");
setDataHomeDirectory();
}
/** Setter for dataHomeDirectory. Will create the directory if not already exists.
*/
private void setDataHomeDirectory() {
/* Follow XDG Base Directory Specification
* https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
*/
if (os.equals("Linux")) {
dataHomeDirectory = System.getenv().get("XDG_DATA_HOME");
if (dataHomeDirectory == null || dataHomeDirectory.equals("")) {
dataHomeDirectory = System.getProperty("user.home") + "/.local/share";
}
} else if (os.equals("Mac")||os.equals("Mac OS X")) {
/* Apple MacOS X User Data Directory
* https://developer.apple.com/library/archive/qa/qa1170/_index.html */
dataHomeDirectory = System.getProperty("user.home") + "/Library";
} else {
dataHomeDirectory = ".";
}
dataHomeDirectory += "/" + projectName + "/";
// create directory if not already exists
new File(dataHomeDirectory).mkdirs();
}
/** Getter for dataHomeDirectory.
* @return path to the application home directory
*/
public String getDataHomeDirectory() {
return dataHomeDirectory;
}
/** Opens dataHomeDirectory if supported.
*/
private void openDataHomeDirectory() {
try {
if (os.equals("Linux")) {
Runtime runtime = Runtime.getRuntime();
runtime.exec(new String[] { "xdg-open", dataHomeDirectory });
} else if (os.equals("Mac")||os.equals("Mac OS X")) {
Runtime runtime = Runtime.getRuntime();
runtime.exec(new String[] { "open", dataHomeDirectory });
}
} catch (IOException e) {
System.out.println("Error encountered while trying to open directory");
}
}
/** Asks the user to choose opening dataHomeDirectory or not.
*/
public void askOpenDataHomeDirectory() {
if (os.equals("Linux") || os.equals("Mac") || os.equals("Mac OS X")) {
System.out.println("Do you want to open this directory? (y/N)");
Scanner scanner = new Scanner(System.in);
String resp = scanner.nextLine();
if (resp.equals("y") || resp.equals("Y")) {
System.out.println("Openning");
openDataHomeDirectory();
}
}
}
}