packages communication,observers cleaned and commented

This commit is contained in:
Cavailles Kevin 2021-02-03 09:36:28 +01:00
parent 0dcfca4937
commit 15a618ec50
16 changed files with 514 additions and 246 deletions

View file

@ -1,38 +1,49 @@
package communication.filetransfer; package communication.filetransfer;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import observers.ObserverInputMessage; import observers.ObserverInputMessage;
public class FileTransferClient { public class FileTransferClient {
private int port; private int port;
private ArrayList<File> files = null; private ArrayList<File> files;
private ObserverInputMessage obsInput; private ObserverInputMessage obsInput;
/**
public FileTransferClient(int port, ArrayList<File> filesToSend, ObserverInputMessage obs) throws UnknownHostException, IOException { * Create a client to transfer one or several files on the specified port of
* localhost. A new Thread is created for each file. The files are sent one by
* one to save bandwidth and avoid issues.
*
* @param port The port of localhost on which to send the files.
* @param filesToSend The file(s) to send.
* @param o The observer to notify each time a file is fully sent.
*/
public FileTransferClient(int port, ArrayList<File> filesToSend, ObserverInputMessage o) {
this.port = port; this.port = port;
this.files = filesToSend; this.files = filesToSend;
this.obsInput = obs; this.obsInput = o;
} }
/**
* Try to send every file on localhost on the specified port with a new thread.
* An observer is passed to the thread and it is notified each time a file is
* fully sent.
*
* @throws IOException
* @throws InterruptedException
*/
public void sendFiles() throws IOException, InterruptedException { public void sendFiles() throws IOException, InterruptedException {
for(File f: this.files) { for (File f : this.files) {
FileTransferSendingThread ftc = new FileTransferSendingThread(this.port, f,this.obsInput); FileTransferSendingThread ftc = new FileTransferSendingThread(this.port, f, this.obsInput);
ftc.start(); ftc.start();
ftc.join(); ftc.join();
} }
} }
} }

View file

@ -1,6 +1,5 @@
package communication.filetransfer; package communication.filetransfer;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
@ -15,20 +14,31 @@ import messages.MessageFichier;
import observers.ObserverInputMessage; import observers.ObserverInputMessage;
public class FileTransferReceivingThread extends Thread { public class FileTransferReceivingThread extends Thread {
private SocketChannel sockTransfert; private SocketChannel sockTransfert;
private ObserverInputMessage obsInput; private ObserverInputMessage obsInput;
public FileTransferReceivingThread(SocketChannel sock, ObserverInputMessage obs) { /**
* Create the thread that will receive one file during a file transfer. This
* allows users to write in the chat while sending/receiving files.
*
* @param sock The SocketChannel returned by ServerSocketChannel.accept().
* @param o The observer to notify once the file is fully received.
*/
public FileTransferReceivingThread(SocketChannel sock, ObserverInputMessage o) {
this.sockTransfert = sock; this.sockTransfert = sock;
this.obsInput = obs; this.obsInput = o;
} }
public void run() { public void run() {
try { try {
int nbByteRead = 0; int nbByteRead = 0;
// Buffer to receive a chunk of the file
ByteBuffer fileData = ByteBuffer.allocate(4 * FileTransferUtils.KB_SIZE); ByteBuffer fileData = ByteBuffer.allocate(4 * FileTransferUtils.KB_SIZE);
// InputStream to read the first object which is a message containing the name
// and size of the file
ObjectInputStream inputFileInformation = new ObjectInputStream( ObjectInputStream inputFileInformation = new ObjectInputStream(
this.sockTransfert.socket().getInputStream()); this.sockTransfert.socket().getInputStream());
@ -41,10 +51,11 @@ public class FileTransferReceivingThread extends Thread {
String[] fileInfo = this.processFileInformation(m); String[] fileInfo = this.processFileInformation(m);
String filePath = FileTransferUtils.DOWNLOADS_RELATIVE_PATH + fileInfo[0]; String filePath = FileTransferUtils.DOWNLOADS_RELATIVE_PATH + fileInfo[0];
long fileSize = Long.parseLong(fileInfo[1]); long fileSize = Long.parseLong(fileInfo[1]);
// OutputStream to create the file if it does not exist
FileOutputStream fOutStream = new FileOutputStream(filePath); FileOutputStream fOutStream = new FileOutputStream(filePath);
// Channel to write the data received in the file
FileChannel fileWriter = fOutStream.getChannel(); FileChannel fileWriter = fOutStream.getChannel();
while (nbTotalBytesRead < fileSize && (nbByteRead = this.sockTransfert.read(fileData)) > 0) { while (nbTotalBytesRead < fileSize && (nbByteRead = this.sockTransfert.read(fileData)) > 0) {
@ -58,11 +69,13 @@ public class FileTransferReceivingThread extends Thread {
fileWriter.close(); fileWriter.close();
fOutStream.close(); fOutStream.close();
inputFileInformation.close();
// Process the message to display (thumbnails in the case of images) and notify
// the observer
Message mUpdate = FileTransferUtils.processMessageToDisplay(new File(filePath)); Message mUpdate = FileTransferUtils.processMessageToDisplay(new File(filePath));
mUpdate.setSender("other"); mUpdate.setSender("other");
this.obsInput.update(this, mUpdate); this.obsInput.updateInput(this, mUpdate);
} catch (IOException | ClassNotFoundException e) { } catch (IOException | ClassNotFoundException e) {
e.printStackTrace(); e.printStackTrace();
@ -75,6 +88,13 @@ public class FileTransferReceivingThread extends Thread {
} }
} }
/**
* Split the content of a message with the separator ";". This function is only
* used to read the name and the size of the file to receive.
*
* @param m message containing the file's information (name and size).
* @return An array with the file's name and the file's size respectively.
*/
private String[] processFileInformation(MessageFichier m) { private String[] processFileInformation(MessageFichier m) {
return m.getContenu().split(";"); return m.getContenu().split(";");
} }

View file

@ -17,33 +17,46 @@ import messages.MessageFichier;
import messages.Message.TypeMessage; import messages.Message.TypeMessage;
import observers.ObserverInputMessage; import observers.ObserverInputMessage;
public class FileTransferSendingThread extends Thread{ public class FileTransferSendingThread extends Thread {
private SocketChannel sockTransfert; private SocketChannel sockTransfert;
private File file; private File file;
private ObserverInputMessage obsInput; private ObserverInputMessage obsInput;
public FileTransferSendingThread(int port, File fileToSend, ObserverInputMessage obs) throws IOException { /**
* Create the thread that will send one file during a file transfer. This allows
* users to write in the chat while sending/receiving files.
*
* @param port The port on localhost to which the SocketChannel will connect.
* @param fileToSend The file to send.
* @param o The observer to notify once the file is fully received.
* @throws IOException if the socket's creation fails.
*/
public FileTransferSendingThread(int port, File fileToSend, ObserverInputMessage o) throws IOException {
SocketChannel sock = SocketChannel.open(); SocketChannel sock = SocketChannel.open();
SocketAddress addr = new InetSocketAddress(port); SocketAddress addr = new InetSocketAddress(port);
sock.connect(addr); sock.connect(addr);
this.sockTransfert = sock; this.sockTransfert = sock;
this.file = fileToSend; this.file = fileToSend;
this.obsInput = obs; this.obsInput = o;
} }
public void run() { public void run() {
try { try {
// Buffer to send a chunk of the file
ByteBuffer fileData = ByteBuffer.allocate(4 * FileTransferUtils.KB_SIZE); ByteBuffer fileData = ByteBuffer.allocate(4 * FileTransferUtils.KB_SIZE);
// OutputStream to write the first object which is a message containing the name
// and size of the file
ObjectOutputStream outputFileInformation = new ObjectOutputStream( ObjectOutputStream outputFileInformation = new ObjectOutputStream(
this.sockTransfert.socket().getOutputStream()); this.sockTransfert.socket().getOutputStream());
// Channel to read the data of the file
FileChannel fileReader = FileChannel.open(Paths.get(file.getPath())); FileChannel fileReader = FileChannel.open(Paths.get(file.getPath()));
String str = file.getName() + ";" + file.getTotalSpace(); String str = file.getName() + ";" + file.getTotalSpace();
// Send file datas (name + size); // Send file data (name + size);
outputFileInformation.writeObject(new MessageFichier(TypeMessage.FICHIER, str, "")); outputFileInformation.writeObject(new MessageFichier(TypeMessage.FICHIER, str, ""));
while (fileReader.read(fileData) > 0) { while (fileReader.read(fileData) > 0) {
@ -53,11 +66,14 @@ public class FileTransferSendingThread extends Thread{
} }
fileReader.close(); fileReader.close();
outputFileInformation.close();
// Process the message to display (thumbnails in the case of images) and notify
// the observer
Message mUpdate = FileTransferUtils.processMessageToDisplay(this.file); Message mUpdate = FileTransferUtils.processMessageToDisplay(this.file);
mUpdate.setSender("Moi"); mUpdate.setSender("Moi");
this.obsInput.update(this, mUpdate); this.obsInput.updateInput(this, mUpdate);
} catch (IOException | MauvaisTypeMessageException e) { } catch (IOException | MauvaisTypeMessageException e) {
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
@ -68,7 +84,5 @@ public class FileTransferSendingThread extends Thread{
} }
} }
} }
} }

View file

@ -8,21 +8,32 @@ import java.nio.channels.SocketChannel;
import observers.ObserverInputMessage; import observers.ObserverInputMessage;
public class FileTransferServer extends Thread { public class FileTransferServer extends Thread {
private ServerSocketChannel sockFTListen; private ServerSocketChannel sockFTListen;
private int nbFile; private int nbFile;
private ObserverInputMessage obsInput; private ObserverInputMessage obsInput;
/**
public FileTransferServer(int nbFile, ObserverInputMessage obs) throws UnknownHostException, IOException { * Create a server to transfer one or several files. A new socket and thread is
* created for each file to receive. The files are received one by one to save
* bandwidth and avoid issues.
*
* @param nbFile The number of file to receive.
* @param o The observer to notify once a file is fully received.
* @throws UnknownHostException
* @throws IOException
*/
public FileTransferServer(int nbFile, ObserverInputMessage o) throws UnknownHostException, IOException {
this.sockFTListen = ServerSocketChannel.open(); this.sockFTListen = ServerSocketChannel.open();
this.sockFTListen.socket().bind(new InetSocketAddress(0)); this.sockFTListen.socket().bind(new InetSocketAddress(0));
this.nbFile = nbFile; this.nbFile = nbFile;
this.obsInput = obs; this.obsInput = o;
} }
/**
* @return The port binded to the ServerSocketChannel.
*/
public int getPort() { public int getPort() {
return this.sockFTListen.socket().getLocalPort(); return this.sockFTListen.socket().getLocalPort();
} }

View file

@ -23,55 +23,76 @@ import messages.Message.TypeMessage;
public class FileTransferUtils { public class FileTransferUtils {
// Relative path of the folder where are put the received files
protected static final String DOWNLOADS_RELATIVE_PATH = "../downloads/"; protected static final String DOWNLOADS_RELATIVE_PATH = "../downloads/";
protected static final ArrayList<String> IMAGE_EXTENSIONS = new ArrayList<String>(List.of("tif","tiff","bmp","jpg","jpeg","gif", "png", "eps", "svg")); protected static final ArrayList<String> IMAGE_EXTENSIONS = new ArrayList<String>(
List.of("tif", "tiff", "bmp", "jpg", "jpeg", "gif", "png", "eps", "svg"));
protected static final int KB_SIZE = 1024; protected static final int KB_SIZE = 1024;
/**
* Process what to display on the chat depending on the file sent/received. A
* thumbnail will be created in the case of an image, A String with the file's
* name will be created otherwise.
*
* @param file The file to process.
* @return A message of which content is either a thumbnail or the file's name.
* @throws IOException
*/
protected static MessageFichier processMessageToDisplay(File file) throws IOException { protected static MessageFichier processMessageToDisplay(File file) throws IOException {
String nameFile = file.getName(); String nameFile = file.getName();
String extension = processFileExtension(nameFile); String extension = processFileExtension(nameFile);
TypeMessage type; TypeMessage type;
String contenu; String contenu;
if(IMAGE_EXTENSIONS.contains(extension)) { if (IMAGE_EXTENSIONS.contains(extension)) {
type = TypeMessage.IMAGE; type = TypeMessage.IMAGE;
BufferedImage img = ImageIO.read(file); BufferedImage img = ImageIO.read(file);
contenu = encodeImage(createThumbnail(img), extension) ; contenu = encodeImage(createThumbnail(img), extension);
}else { } else {
type = TypeMessage.FICHIER; type = TypeMessage.FICHIER;
contenu = nameFile; contenu = nameFile;
} }
try { try {
//return new MessageFichier(type, contenu, extension);
return new MessageFichier(type, contenu, extension); return new MessageFichier(type, contenu, extension);
} catch (MauvaisTypeMessageException e) { } catch (MauvaisTypeMessageException e) {
System.out.println("Should never go in");
e.printStackTrace(); e.printStackTrace();
} }
return null; return null;
} }
/**
* @param fileName The name of the file (with its extension).
* @return The extension of the file.
*/
protected static String processFileExtension(String fileName) { protected static String processFileExtension(String fileName) {
String extension = ""; String extension = "";
int i = fileName.indexOf('.'); int i = fileName.indexOf('.');
if (i >= 0 || i != -1) { if (i >= 0 || i != -1) {
extension = fileName.substring(i+1).toLowerCase(); extension = fileName.substring(i + 1).toLowerCase();
} }
return extension; return extension;
} }
/**
private static BufferedImage createThumbnail(BufferedImage image){ * @param image A buffered image.
* @return A thumbnail of the image.
*/
private static BufferedImage createThumbnail(BufferedImage image) {
float w = image.getWidth(); float w = image.getWidth();
float ratio = (w > 150) ? (150F/w) : 1; float ratio = (w > 150) ? (150F / w) : 1;
BufferedImage scaled = scale(image, ratio); BufferedImage scaled = scale(image, ratio);
return scaled; return scaled;
} }
/**
* @param img A buffered image.
* @param extension The extension of the image.
* @return The base64 encoded string corresponding to the given image.
* @throws IOException
*/
private static String encodeImage(BufferedImage img, String extension) throws IOException { private static String encodeImage(BufferedImage img, String extension) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
ImageIO.write(img, extension, bos); ImageIO.write(img, extension, bos);
@ -79,16 +100,21 @@ public class FileTransferUtils {
bos.close(); bos.close();
return imgString; return imgString;
} }
/**
* @param imageString The base64 encoded string of an image.
* @return A buffered image corresponding to the given base64 encoded string.
* @throws IOException
*/
public static BufferedImage decodeImage(String imageString) throws IOException { public static BufferedImage decodeImage(String imageString) throws IOException {
byte[] imgData = Base64.getDecoder().decode(imageString); byte[] imgData = Base64.getDecoder().decode(imageString);
InputStream is = new ByteArrayInputStream(imgData); InputStream is = new ByteArrayInputStream(imgData);
BufferedImage img = ImageIO.read(is); BufferedImage img = ImageIO.read(is);
is.close(); is.close();
return img; return img;
} }
// Used to scale an image with a given ratio
private static BufferedImage scale(BufferedImage source, double ratio) { private static BufferedImage scale(BufferedImage source, double ratio) {
int w = (int) (source.getWidth() * ratio); int w = (int) (source.getWidth() * ratio);
int h = (int) (source.getHeight() * ratio); int h = (int) (source.getHeight() * ratio);
@ -109,11 +135,15 @@ public class FileTransferUtils {
BufferedImage image = gc.createCompatibleImage(w, h); BufferedImage image = gc.createCompatibleImage(w, h);
return image; return image;
} }
/**
* Create the folder with the path "DOWNLOADS_RELATIVE_PATH" if it does not
* exist.
*/
public static void createDownloads() { public static void createDownloads() {
File downloads = new File(FileTransferUtils.DOWNLOADS_RELATIVE_PATH); File downloads = new File(FileTransferUtils.DOWNLOADS_RELATIVE_PATH);
if(!downloads.exists()) { if (!downloads.exists()) {
downloads.mkdir(); downloads.mkdir();
} }
} }

View file

@ -5,63 +5,86 @@ import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket; import java.net.Socket;
import observers.ObserverInputMessage; import observers.ObserverInputMessage;
import observers.ObserverSocketState; import observers.ObserverSocketState;
import messages.MauvaisTypeMessageException;
import messages.Message; import messages.Message;
public class TCPClient { public class TCPClient {
private Socket sockTCP; private Socket sockTCP;
private ObjectOutputStream output; private ObjectOutputStream output;
private ObjectInputStream input;
private TCPInputThread inputThread; private TCPInputThread inputThread;
/**
* Create a TCP client from an existing socket. It will ensure the transmission
* of messages during a session. Two ObjectStream are created in order to
* read/write messages. The ObjectInputStream is given to a thread to read
* continuously the incoming message and allowing the user to write a message
* anytime.
*
* @param sockTCP
* @throws IOException
*/
public TCPClient(Socket sockTCP) throws IOException { public TCPClient(Socket sockTCP) throws IOException {
this.sockTCP = sockTCP; this.sockTCP = sockTCP;
this.output = new ObjectOutputStream(sockTCP.getOutputStream()); this.output = new ObjectOutputStream(sockTCP.getOutputStream());
ObjectInputStream input = new ObjectInputStream(sockTCP.getInputStream()); this.input = new ObjectInputStream(sockTCP.getInputStream());
this.inputThread = new TCPInputThread(input); this.inputThread = new TCPInputThread(this.input);
}
public TCPClient(InetAddress addr, int port) throws IOException {
this(new Socket(addr, port));
} }
/**
* Start the thread that will continuously read from the socket.
*/
public void startInputThread() { public void startInputThread() {
this.inputThread.start(); this.inputThread.start();
} }
/**
public void sendMessage(Message message) throws IOException, MauvaisTypeMessageException { * Send a message by writing it in the ObjectOutputStream of the socket
System.out.println("dans write"); *
* @param message
* @throws IOException
*/
public void sendMessage(Message message) throws IOException {
this.output.writeObject(message); this.output.writeObject(message);
} }
/**
* Set the observer to notify when a message is received
*
* @param o The observer
*/
public void setObserverInputThread(ObserverInputMessage o) { public void setObserverInputThread(ObserverInputMessage o) {
this.inputThread.setObserverInputMessage(o); this.inputThread.setObserverInputMessage(o);
} }
/**
* Set the observer to notify when the session is closed/the communication is
* broken.
*
* @param o The observer
*/
public void setObserverSocketState(ObserverSocketState o) { public void setObserverSocketState(ObserverSocketState o) {
this.inputThread.setObserverSocketState(o); this.inputThread.setObserverSocketState(o);
} }
/**
* Method used when the session is over. Set all attribute references to null,
* interrupt the inputThread and close the streams and the socket.
*/
public void destroyAll() { public void destroyAll() {
try { try {
if (!this.sockTCP.isClosed()) { if (!this.sockTCP.isClosed()) {
this.inputThread.setObserverSocketState(null);
this.inputThread.interrupt();
this.input.close();
this.output.close(); this.output.close();
this.sockTCP.close(); this.sockTCP.close();
this.inputThread.setObserverSocketState(null);
} }
this.inputThread = null; this.inputThread = null;
this.sockTCP = null; this.sockTCP = null;

View file

@ -12,7 +12,12 @@ public class TCPInputThread extends Thread {
private ObserverInputMessage obsInput; private ObserverInputMessage obsInput;
private ObserverSocketState obsState; private ObserverSocketState obsState;
public TCPInputThread(ObjectInputStream input) { /**
* Create the thread used to read the messages
*
* @param input The ObjectInputStream to read data from
*/
protected TCPInputThread(ObjectInputStream input) {
this.input = input; this.input = input;
this.running = true; this.running = true;
} }
@ -22,10 +27,9 @@ public class TCPInputThread extends Thread {
while (this.running) { while (this.running) {
try { try {
Object o = this.input.readObject();
System.out.println("dans read"); // Notify the observer a message was received
Object o = this.input.readObject(); this.obsInput.updateInput(this, o);
this.obsInput.update(this, o);
} catch (IOException | ClassNotFoundException e) { } catch (IOException | ClassNotFoundException e) {
this.interrupt(); this.interrupt();
@ -37,34 +41,35 @@ public class TCPInputThread extends Thread {
@Override @Override
public void interrupt() { public void interrupt() {
// Stop the thread
try { this.running = false;
//Stop the thread // Close the stream and the socket
this.running = false;
//Close the stream and the socket if (this.obsState != null) {
this.input.close(); // Send an update to the controller
this.obsState.updateSocketState(this, true);
if(this.obsState != null) {
//Send an update to the controller
this.obsState.updateSocketState(this, true);
}
//Set every attribute to null so they're collected by the GC
this.obsInput = null;
this.obsState = null;
this.input = null;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} }
// Set every attribute to null so they're collected by the GC
this.obsInput = null;
this.obsState = null;
this.input = null;
} }
/**
* Set the observer to notify when a message is received
*
* @param o The observer
*/
protected void setObserverInputMessage(ObserverInputMessage o) { protected void setObserverInputMessage(ObserverInputMessage o) {
this.obsInput = o; this.obsInput = o;
} }
/**
* Set the observer to notify when the session is cut/closed.
*
* @param o The observer
*/
protected void setObserverSocketState(ObserverSocketState o) { protected void setObserverSocketState(ObserverSocketState o) {
this.obsState = o; this.obsState = o;
} }

View file

@ -8,36 +8,45 @@ import java.net.UnknownHostException;
import observers.ObserverInputMessage; import observers.ObserverInputMessage;
public class TCPServer extends Thread { public class TCPServer extends Thread {
//****
public static int PORT_SERVER = 7000;
private ServerSocket sockListenTCP; private ServerSocket sockListenTCP;
private ObserverInputMessage obs; private ObserverInputMessage obsInput;
/**
* Create a TCP Server on the specified port. It will listen continuously for
* connections in order to create new sessions between users.
*
* @param port The port on which the server will listen
* @throws UnknownHostException
* @throws IOException
*/
public TCPServer(int port) throws UnknownHostException, IOException { public TCPServer(int port) throws UnknownHostException, IOException {
this.sockListenTCP = new ServerSocket(port, 50, InetAddress.getLocalHost()); this.sockListenTCP = new ServerSocket(port, 50, InetAddress.getLocalHost());
} }
@Override @Override
public void run() { public void run() {
System.out.println("TCP running");
Socket sockAccept; Socket sockAccept;
while(true) { while (true) {
try { try {
sockAccept = this.sockListenTCP.accept(); sockAccept = this.sockListenTCP.accept();
this.obs.update(this, sockAccept);
// Notify the observer of the new connexion
this.obsInput.updateInput(this, sockAccept);
} catch (IOException e) { } catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
} }
} }
public void addObserver(ObserverInputMessage obs) { /**
this.obs = obs; * Set the observer to notify when a new connection is made.
*
* @param o The observer
*/
public void addObserver(ObserverInputMessage o) {
this.obsInput = o;
} }
} }

View file

@ -2,7 +2,6 @@ package communication.udp;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
@ -13,18 +12,23 @@ import observers.ObserverUserList;
public class CommunicationUDP extends Thread { public class CommunicationUDP extends Thread {
// ****
protected static int PORT_SERVEUR = 3000;
// ****
protected static int PORT_CLIENT = 2000;
private UDPClient client; private UDPClient client;
private UDPServer server; private UDPServer server;
private int portServer; private int portServer;
private ArrayList<Integer> portOthers; private ArrayList<Integer> portOthers;
private ArrayList<Utilisateur> users = new ArrayList<Utilisateur>(); private ArrayList<Utilisateur> users = new ArrayList<Utilisateur>();
private ObserverUserList observer; private ObserverUserList obsList;
/**
* Create the class that will manage the userlist and contain a UDPClient and a
* UDPServer. Since the applications will run on localhost, it needs to know
* every UDPServer ports used in order to replicate a broadcast behaviour.
*
* @param portClient The port number for the UDPClient
* @param portServer The port number for the UDPServer
* @param portsOther The port numbers for every other application's UDPServer
* @throws IOException
*/
public CommunicationUDP(int portClient, int portServer, int[] portsOther) throws IOException { public CommunicationUDP(int portClient, int portServer, int[] portsOther) throws IOException {
this.portServer = portServer; this.portServer = portServer;
this.portOthers = this.getArrayListFromArray(portsOther); this.portOthers = this.getArrayListFromArray(portsOther);
@ -33,14 +37,13 @@ public class CommunicationUDP extends Thread {
this.client = new UDPClient(portClient); this.client = new UDPClient(portClient);
} }
// **** /**
public CommunicationUDP() throws SocketException, UnknownHostException { * Create an ArrayList<Integer> from the int[] list of every servers' ports and
this.portServer = PORT_SERVEUR; * remove the port of this application UDPServer.
this.server = new UDPServer(portServer, this); *
this.server.start(); * @param ports The UDPServer port numbers.
this.client = new UDPClient(PORT_CLIENT); * @return An ArrayList<Integer> without the port of this UDPServer.
} */
private ArrayList<Integer> getArrayListFromArray(int ports[]) { private ArrayList<Integer> getArrayListFromArray(int ports[]) {
ArrayList<Integer> tmp = new ArrayList<Integer>(); ArrayList<Integer> tmp = new ArrayList<Integer>();
for (int port : ports) { for (int port : ports) {
@ -51,42 +54,83 @@ public class CommunicationUDP extends Thread {
return tmp; return tmp;
} }
public void setObserver(ObserverUserList obs) { /**
this.observer = obs; * Set the observer to notify when the userList is updated.
*
* @param obs The observer
*/
public void setObserver(ObserverUserList o) {
this.obsList = o;
} }
// -------------- USER LIST UPDATE FUNCTION --------------// // -------------- USER LIST UPDATE FUNCTION --------------//
protected synchronized void addUser(String idClient, String pseudoClient, InetAddress ipClient, int port) /**
throws IOException { * Add a new user to the userlist and notify the observer.
users.add(new Utilisateur(idClient, pseudoClient, ipClient, port)); *
* @param idClient
* @param pseudoClient
* @param ipClient
* @param port
* @throws UnknownHostException
*/
protected synchronized void addUser(String idUser, String pseudoUser, InetAddress ipUser, int portTCPServer)
throws UnknownHostException {
users.add(new Utilisateur(idUser, pseudoUser, ipUser, portTCPServer));
this.sendUpdate(); this.sendUpdate();
} }
protected synchronized void changePseudoUser(String idClient, String pseudoClient, InetAddress ipClient, int port) { /**
int index = getIndexFromID(idClient); * Change the pseudo of an user and notify the observer if it exists in the
users.get(index).setPseudo(pseudoClient); * userlist. Do nothing otherwise.
this.sendUpdate(); *
* @param idClient
* @param pseudoClient
* @param ipClient
* @param port
*/
protected synchronized void changePseudoUser(String idUser, String pseudoUser, InetAddress ipUser,
int portTCPServer) {
int index = getIndexFromID(idUser);
if (index != -1) {
users.get(index).setPseudo(pseudoUser);
this.sendUpdate();
}
} }
protected synchronized void removeUser(String idClient, String pseudoClient, InetAddress ipClient, int port) { /**
int index = getIndexFromID(idClient); * Remove an user from the userlist and notify the observer if it exists in the
* userlist. Do nothing otherwise.
*
* @param idUser
* @param pseudoUser
* @param ipUser
* @param portTCPServer
*/
protected synchronized void removeUser(String idUser, String pseudoUser, InetAddress ipUser, int portTCPServer) {
int index = getIndexFromID(idUser);
if (index != -1) { if (index != -1) {
users.remove(index); users.remove(index);
this.sendUpdate();
} }
this.sendUpdate();
} }
public void removeAll() { public void removeAllUsers() {
int oSize = users.size(); this.users.clear();
for (int i = 0; i < oSize; i++) {
users.remove(0);
}
} }
// -------------- CHECKERS --------------// // -------------- CHECKERS --------------//
/**
* Check if there is an user in the list that has the given id.
*
* @param id The user's id.
* @return true if the user is in the list
* false otherwise.
*/
protected boolean containsUserFromID(String id) { protected boolean containsUserFromID(String id) {
for (Utilisateur u : users) { for (Utilisateur u : users) {
if (u.getId().equals(id)) { if (u.getId().equals(id)) {
@ -96,6 +140,13 @@ public class CommunicationUDP extends Thread {
return false; return false;
} }
/**
* Check if there is an user in the list that has the given pseudo.
*
* @param pseudo The user's pseudo.
* @return true if the user is in the list
* false otherwise.
*/
public boolean containsUserFromPseudo(String pseudo) { public boolean containsUserFromPseudo(String pseudo) {
for (Utilisateur u : users) { for (Utilisateur u : users) {
if (u.getPseudo().toLowerCase().equals(pseudo)) { if (u.getPseudo().toLowerCase().equals(pseudo)) {
@ -108,6 +159,13 @@ public class CommunicationUDP extends Thread {
// -------------- GETTERS --------------// // -------------- GETTERS --------------//
/**
* Return the user with the given pseudo if it exists in the list.
*
* @param pseudo The user's pseudo.
* @return The user if it exists in the list.
* null otherwise.
*/
public Utilisateur getUserFromPseudo(String pseudo) { public Utilisateur getUserFromPseudo(String pseudo) {
for (int i = 0; i < users.size(); i++) { for (int i = 0; i < users.size(); i++) {
if (users.get(i).getPseudo().equals(pseudo)) { if (users.get(i).getPseudo().equals(pseudo)) {
@ -117,6 +175,13 @@ public class CommunicationUDP extends Thread {
return null; return null;
} }
/**
* Return the index of the user with the given id if it exists in the list.
*
* @param id The user's id.
* @return The index if the user exists in the list.
* null otherwise
*/
private int getIndexFromID(String id) { private int getIndexFromID(String id) {
for (int i = 0; i < users.size(); i++) { for (int i = 0; i < users.size(); i++) {
if (users.get(i).getId().equals(id)) { if (users.get(i).getId().equals(id)) {
@ -128,79 +193,100 @@ public class CommunicationUDP extends Thread {
// -------------- SEND MESSAGES --------------// // -------------- SEND MESSAGES --------------//
/**
* Send a message indicating this application's user is connected to every
* UDPServer.
*
* @throws UnknownHostException
* @throws IOException
*/
public void sendMessageConnecte() throws UnknownHostException, IOException { public void sendMessageConnecte() throws UnknownHostException, IOException {
for (int port : this.portOthers) {
try { try {
this.client.sendMessageUDP_local(new MessageSysteme(Message.TypeMessage.JE_SUIS_CONNECTE), port, Message msgOut = new MessageSysteme(Message.TypeMessage.JE_SUIS_CONNECTE);
InetAddress.getLocalHost()); for (int port : this.portOthers) {
} catch (MauvaisTypeMessageException e) {
/* Si ça marche pas essayer là */} this.client.sendMessageUDP_local(msgOut, port);
}
} catch (MauvaisTypeMessageException e) {
e.printStackTrace();
} }
} }
// Send the message "add,id,pseudo" to localhost on all the ports in /**
// "portOthers" * Send a message containing this application's user's data to every UDPServer.
// This allows the receivers' agent (portOthers) to create or modify an entry * This method is used to first add this user in the userlist or update this
// with the * user's pseudo.
// data of this agent *
// Typically used to notify of a name change * @throws UnknownHostException
* @throws IOException
*/
public void sendMessageInfoPseudo() throws UnknownHostException, IOException { public void sendMessageInfoPseudo() throws UnknownHostException, IOException {
Utilisateur self = Utilisateur.getSelf(); Utilisateur self = Utilisateur.getSelf();
try { try {
Message msgOut = new MessageSysteme(Message.TypeMessage.INFO_PSEUDO, self.getPseudo(), self.getId(), self.getPort()); Message msgOut = new MessageSysteme(Message.TypeMessage.INFO_PSEUDO, self.getPseudo(), self.getId(),
self.getPort());
for (int port : this.portOthers) { for (int port : this.portOthers) {
this.client.sendMessageUDP_local(msgOut, port, InetAddress.getLocalHost()); this.client.sendMessageUDP_local(msgOut, port);
} }
} catch (Exception e) { } catch (MauvaisTypeMessageException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
// Same, but on only one port /**
// Typically used to give your current name and id to a newly arrived host * Send a message containing this application's user's data to one user. This
* method is used to answer back when receiving a message with the type
* "JE_SUIS_CONNECTE"
*
* @param portOther The port on which the other user's UDPServer is listening
* @throws UnknownHostException
* @throws IOException
*/
public void sendMessageInfoPseudo(int portOther) throws UnknownHostException, IOException { public void sendMessageInfoPseudo(int portOther) throws UnknownHostException, IOException {
Utilisateur self = Utilisateur.getSelf(); Utilisateur self = Utilisateur.getSelf();
try { try {
Message msgOut = new MessageSysteme(Message.TypeMessage.INFO_PSEUDO, self.getPseudo(), self.getId(), Message msgOut = new MessageSysteme(Message.TypeMessage.INFO_PSEUDO, self.getPseudo(), self.getId(),
self.getPort()); self.getPort());
this.client.sendMessageUDP_local(msgOut, portOther, InetAddress.getLocalHost()); this.client.sendMessageUDP_local(msgOut, portOther);
} catch (MauvaisTypeMessageException e) { } catch (MauvaisTypeMessageException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
// Send the message "del,id,pseudo" to localhost on all the ports in /**
// "portOthers" * Send a message indicating this application's user is disconnected to every
// This allows the receivers' agent (portOthers) to delete the entry * UDPServer.
// corresponding to this agent *
* @throws UnknownHostException
* @throws IOException
*/
public void sendMessageDelete() throws UnknownHostException, IOException { public void sendMessageDelete() throws UnknownHostException, IOException {
Utilisateur self = Utilisateur.getSelf(); Utilisateur self = Utilisateur.getSelf();
try { try {
Message msgOut = new MessageSysteme(Message.TypeMessage.JE_SUIS_DECONNECTE, self.getPseudo(), self.getId(), self.getPort()); Message msgOut = new MessageSysteme(Message.TypeMessage.JE_SUIS_DECONNECTE, self.getPseudo(), self.getId(),
self.getPort());
for (int port : this.portOthers) { for (int port : this.portOthers) {
this.client.sendMessageUDP_local(msgOut, port, InetAddress.getLocalHost()); this.client.sendMessageUDP_local(msgOut, port);
} }
} catch (MauvaisTypeMessageException e) { } catch (MauvaisTypeMessageException e) {
e.printStackTrace();
} }
} }
/**
* Notify the observer with the updated list
*/
private void sendUpdate() { private void sendUpdate() {
if(this.observer != null) { if (this.obsList != null) {
this.observer.updateList(this, users); this.obsList.updateList(this, users);
} }
} }
public void destroyAll() {
this.client.destroyAll();
this.server.interrupt();
}
} }

View file

@ -4,40 +4,52 @@ import java.io.IOException;
import java.net.DatagramPacket; import java.net.DatagramPacket;
import java.net.DatagramSocket; import java.net.DatagramSocket;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException; import java.net.SocketException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import messages.*; import messages.*;
public class UDPClient { class UDPClient {
private DatagramSocket sockUDP; private DatagramSocket sockUDP;
private InetAddress broadcast;
/**
* Create a UDP client on the specified port. It will be used to notify the
* other users of this application's user state (Connected/Disconnected/Pseudo
* changed).
*
* @param port
* @throws SocketException
* @throws UnknownHostException
*/
public UDPClient(int port) throws SocketException, UnknownHostException { public UDPClient(int port) throws SocketException, UnknownHostException {
this.sockUDP = new DatagramSocket(port); this.sockUDP = new DatagramSocket(port);
InetAddress localHost = InetAddress.getLocalHost();
NetworkInterface networkInterface = NetworkInterface.getByInetAddress(localHost);
this.broadcast = networkInterface.getInterfaceAddresses().get(0).getBroadcast();
System.out.println(this.broadcast);
System.out.println(InetAddress.getLocalHost());
} }
/**
//Send a message casted as string to the specified port on localhost * Send a message to the specified port on localhost.
protected void sendMessageUDP_local(Message message, int port, InetAddress clientAddress) throws IOException { *
String messageString= message.toString(); * @param message
DatagramPacket outpacket = new DatagramPacket(messageString.getBytes(), messageString.length(), clientAddress, port); * @param port
* @throws IOException
*/
protected void sendMessageUDP_local(Message message, int port) throws IOException {
sendMessageUDP(message, port, InetAddress.getLocalHost());
}
/**
* Send a message to the given address on the specified port.
*
* @param message
* @param port
* @param clientAddress
* @throws IOException
*/
private void sendMessageUDP(Message message, int port, InetAddress clientAddress) throws IOException {
String messageString = message.toString();
DatagramPacket outpacket = new DatagramPacket(messageString.getBytes(), messageString.length(), clientAddress,
port);
this.sockUDP.send(outpacket); this.sockUDP.send(outpacket);
} }
protected void destroyAll() {
this.sockUDP.close();
this.sockUDP = null;
this.broadcast = null;
}
} }

View file

@ -8,13 +8,22 @@ import java.net.SocketException;
import main.Utilisateur; import main.Utilisateur;
import messages.*; import messages.*;
public class UDPServer extends Thread { class UDPServer extends Thread {
private DatagramSocket sockUDP; private DatagramSocket sockUDP;
private CommunicationUDP commUDP; private CommunicationUDP commUDP;
private byte[] buffer; private byte[] buffer;
private boolean running; private boolean running;
/**
* Create a UDP Server on the specified port. It will be used to read the
* other users states (Connected/Disconnected/Pseudo).
*
* @param port
* @param commUDP
* @throws SocketException
*/
public UDPServer(int port, CommunicationUDP commUDP) throws SocketException { public UDPServer(int port, CommunicationUDP commUDP) throws SocketException {
this.running = true; this.running = true;
this.commUDP = commUDP; this.commUDP = commUDP;
@ -27,12 +36,14 @@ public class UDPServer extends Thread {
while (this.running) { while (this.running) {
try { try {
//When a datagram is received, converts its data in a Message
DatagramPacket inPacket = new DatagramPacket(buffer, buffer.length); DatagramPacket inPacket = new DatagramPacket(buffer, buffer.length);
this.sockUDP.receive(inPacket); this.sockUDP.receive(inPacket);
String msgString = new String(inPacket.getData(), 0, inPacket.getLength()); String msgString = new String(inPacket.getData(), 0, inPacket.getLength());
Message msg = Message.stringToMessage(msgString); Message msg = Message.stringToMessage(msgString);
//Depending on the type of the message
switch (msg.getTypeMessage()) { switch (msg.getTypeMessage()) {
case JE_SUIS_CONNECTE: case JE_SUIS_CONNECTE:
@ -40,34 +51,38 @@ public class UDPServer extends Thread {
int portClient = inPacket.getPort(); int portClient = inPacket.getPort();
int portServer = portClient+1; int portServer = portClient+1;
//Answer back with this application's user data
this.commUDP.sendMessageInfoPseudo(portServer); this.commUDP.sendMessageInfoPseudo(portServer);
} }
break; break;
case INFO_PSEUDO: case INFO_PSEUDO:
MessageSysteme m = (MessageSysteme) msg; MessageSysteme m = (MessageSysteme) msg;
//Update the userlist with the data received (Add the user or update it)
if (this.commUDP.containsUserFromID(m.getId())) { if (this.commUDP.containsUserFromID(m.getId())) {
this.commUDP.changePseudoUser(m.getId(), m.getPseudo(), inPacket.getAddress(), m.getPort()); this.commUDP.changePseudoUser(m.getId(), m.getPseudo(), inPacket.getAddress(), m.getPort());
} else { } else {
this.commUDP.addUser(m.getId(), m.getPseudo(), inPacket.getAddress(), m.getPort()); this.commUDP.addUser(m.getId(), m.getPseudo(), inPacket.getAddress(), m.getPort());
System.out.println(m.getId() + ", " + m.getPseudo());
} }
break; break;
case JE_SUIS_DECONNECTE: case JE_SUIS_DECONNECTE:
this.commUDP.removeUser(((MessageSysteme) msg).getId(), ((MessageSysteme) msg).getPseudo(),
inPacket.getAddress(), inPacket.getPort()); MessageSysteme m2 = (MessageSysteme) msg;
//Remove the user from the userlist
this.commUDP.removeUser(m2.getId(), m2.getPseudo(), inPacket.getAddress(), m2.getPort());
break; break;
default: // Others types of messages are ignored because they are supposed to be //Do nothing
// transmitted by TCP and not UDP default:
} }
} catch (IOException e) { } catch (IOException e) {
System.out.println("receive exception"); e.printStackTrace();
} }
} }

View file

@ -1,5 +1,12 @@
package observers; package observers;
public interface ObserverInputMessage { public interface ObserverInputMessage {
public void update(Object o, Object arg);
/**
* Method called when data is received from a TCP socket
*
* @param o : The observer to notify
* @param arg : An object
*/
public void updateInput(Object o, Object arg);
} }

View file

@ -2,6 +2,12 @@ package observers;
public interface ObserverSocketState { public interface ObserverSocketState {
/**
* Method called when a TCP socket is closed/a communication is broken
*
* @param o : The observer to notify
* @param arg : An object
*/
public void updateSocketState(Object o, Object arg); public void updateSocketState(Object o, Object arg);
} }

View file

@ -6,6 +6,12 @@ import main.Utilisateur;
public interface ObserverUserList { public interface ObserverUserList {
/**
* Method called when the userlist is updated
*
* @param o : The observer to notify
* @param userList : The userlist
*/
public void updateList(Object o, ArrayList<Utilisateur> userList); public void updateList(Object o, ArrayList<Utilisateur> userList);
} }

View file

@ -39,6 +39,16 @@ public class ControleurSession implements ActionListener, ObserverInputMessage,
private SQLiteManager sqlManager; private SQLiteManager sqlManager;
private ArrayList<File> files; private ArrayList<File> files;
/**
*
* @param vue
* @param socketComm
* @param idOther
* @param pseudoOther
* @param sqlManager
* @throws IOException
*/
protected ControleurSession(VueSession vue, Socket socketComm, String idOther, String pseudoOther, SQLiteManager sqlManager) throws IOException { protected ControleurSession(VueSession vue, Socket socketComm, String idOther, String pseudoOther, SQLiteManager sqlManager) throws IOException {
this.vue = vue; this.vue = vue;
this.tcpClient = new TCPClient(socketComm); this.tcpClient = new TCPClient(socketComm);
@ -60,7 +70,7 @@ public class ControleurSession implements ActionListener, ObserverInputMessage,
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
//Quand le bouton envoyer est presse //If the button "Envoyer" is pressed
if ((JButton) e.getSource() == this.vue.getButtonEnvoyer()) { if ((JButton) e.getSource() == this.vue.getButtonEnvoyer()) {
String messageContent = this.vue.getInputedText(); String messageContent = this.vue.getInputedText();
System.out.println(messageContent); System.out.println(messageContent);
@ -92,8 +102,7 @@ public class ControleurSession implements ActionListener, ObserverInputMessage,
try { try {
this.tcpClient.sendMessage(messageOut); this.tcpClient.sendMessage(messageOut);
} catch (MauvaisTypeMessageException | IOException e1) { } catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace(); e1.printStackTrace();
} }
@ -105,7 +114,9 @@ public class ControleurSession implements ActionListener, ObserverInputMessage,
} }
} }
//If the button "Importer" is pressed
if((JButton) e.getSource() == this.vue.getButtonImportFile()) { if((JButton) e.getSource() == this.vue.getButtonImportFile()) {
//Display a file chooser to select one or several files
JFileChooser fc = new JFileChooser(); JFileChooser fc = new JFileChooser();
fc.setMultiSelectionEnabled(true); fc.setMultiSelectionEnabled(true);
int returVal = fc.showDialog(this.vue, "Importer"); int returVal = fc.showDialog(this.vue, "Importer");
@ -126,10 +137,7 @@ public class ControleurSession implements ActionListener, ObserverInputMessage,
@Override @Override
public void keyTyped(KeyEvent e) { public void keyTyped(KeyEvent e) {}
// TODO Auto-generated method stub
}
@Override @Override
public void keyPressed(KeyEvent e) { public void keyPressed(KeyEvent e) {
@ -142,10 +150,7 @@ public class ControleurSession implements ActionListener, ObserverInputMessage,
} }
@Override @Override
public void keyReleased(KeyEvent e) { public void keyReleased(KeyEvent e) {}
// TODO Auto-generated method stub
}
protected ArrayList<Message> getHistorique(){ protected ArrayList<Message> getHistorique(){
@ -189,10 +194,11 @@ public class ControleurSession implements ActionListener, ObserverInputMessage,
} }
} }
//Methode appelee quand l'inputStream de la socket de communication recoit des donnees //Method called when a message is received from the TCP socket
@Override @Override
public void update(Object o, Object arg) { public void updateInput(Object o, Object arg) {
Message message = (Message) arg; Message message = (Message) arg;
switch(message.getTypeMessage()) { switch(message.getTypeMessage()) {
case TEXTE: case TEXTE:
System.out.println(message.toString()); System.out.println(message.toString());
@ -217,6 +223,7 @@ public class ControleurSession implements ActionListener, ObserverInputMessage,
this.messagesIn.add(message); this.messagesIn.add(message);
} }
break; break;
case FICHIER_INIT: case FICHIER_INIT:
try { try {
MessageFichier mFichier = (MessageFichier) arg; MessageFichier mFichier = (MessageFichier) arg;
@ -245,16 +252,23 @@ public class ControleurSession implements ActionListener, ObserverInputMessage,
} }
break; break;
//Do nothing
default: default:
} }
} }
//If the other user closes the session or the communication is broken
//Disable the view (TextArea, Buttons..) and display a message
@Override @Override
public void updateSocketState(Object o, Object arg) { public void updateSocketState(Object o, Object arg) {
this.vue.endSession(this.pseudoOther); this.vue.endSession(this.pseudoOther);
} }
/**
*
*/
protected void destroyAll() { protected void destroyAll() {
String idSelf = Utilisateur.getSelf().getId(); String idSelf = Utilisateur.getSelf().getId();
String idOther = this.idOther; String idOther = this.idOther;
@ -263,11 +277,9 @@ public class ControleurSession implements ActionListener, ObserverInputMessage,
this.sqlManager.insertAllMessages(messagesOut, idSelf, idOther); this.sqlManager.insertAllMessages(messagesOut, idSelf, idOther);
this.sqlManager.insertAllMessages(messagesIn, idOther, idSelf); this.sqlManager.insertAllMessages(messagesIn, idOther, idSelf);
} catch (SQLException e) { } catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
this.vue = null; this.vue = null;
this.tcpClient.destroyAll(); this.tcpClient.destroyAll();
this.tcpClient = null; this.tcpClient = null;

View file

@ -245,7 +245,7 @@ public class ControleurStandard implements ActionListener, ListSelectionListener
// ------------OBSERVERS-------------// // ------------OBSERVERS-------------//
@Override @Override
public void update(Object o, Object arg) { public void updateInput(Object o, Object arg) {
if (o == this.tcpServ) { if (o == this.tcpServ) {
@ -304,6 +304,7 @@ public class ControleurStandard implements ActionListener, ListSelectionListener
private void setVueConnexion() throws UnknownHostException, IOException { private void setVueConnexion() throws UnknownHostException, IOException {
this.commUDP.sendMessageDelete(); this.commUDP.sendMessageDelete();
this.commUDP.removeAllUsers();
this.vue.removeAllUsers(); this.vue.removeAllUsers();
this.vue.closeAllSession(); this.vue.closeAllSession();
this.idsSessionEnCours.clear(); this.idsSessionEnCours.clear();