Compare commits

..

32 commits

Author SHA1 Message Date
m-gues
a2a091206b ajouté commentaires descriptifs 2021-02-15 23:33:55 +01:00
m-gues
d3adc16146 correction typo 2021-02-15 12:25:51 +01:00
m-gues
6a4886396d dernier diagramme de classe 2021-02-15 01:51:56 +01:00
m-gues
3fe2faa785 version finale du code serveur 2021-02-15 01:35:40 +01:00
m-gues
dbd3861814 dernière version de l'appli + nettoyage code serveur 2021-02-14 22:58:07 +01:00
m-gues
862d469da1 serveur code version finale 2021-02-13 22:38:21 +01:00
m-gues
cc82a17bd7 serveur code version finale 2021-02-13 22:36:20 +01:00
m-gues
37b2de9cde essai de BDD 2021-02-07 17:48:34 +01:00
m-gues
0caf039c1c tentative de BDD avec le servlet 2021-01-31 18:39:35 +01:00
m-gues
a66c73dc75 affichage utilisateurs actifs terminé 2021-01-31 13:48:03 +01:00
m-gues
d5ca5d66f7 Serveur presence fonctionnel (sans la liste des utilisateurs affichee 2021-01-16 01:43:59 +01:00
m-gues
d292b3de1b recup application qui fonctionne 2021-01-12 10:04:03 +01:00
m-gues
bcc4e7c00c recup application qui fonctionne 2021-01-12 10:03:02 +01:00
m-gues
2d321d7103 comUDP du servlet modifiee, attente de reponse sur HHTPServletRequest 2021-01-09 17:16:07 +01:00
m-gues
80a3dcc958 subscribe, publish, notify et update v1 2020-12-23 20:08:53 +01:00
m-gues
abf8c14bb2 remaniament squelette servler 2020-12-22 23:54:41 +01:00
m-gues
2545058ee3 avant checkout application 2020-12-22 23:27:28 +01:00
m-gues
d1b75e16bf avant checkout application 2020-12-22 23:26:55 +01:00
m-gues
ea734e0ccc avant checkout application 2020-12-22 23:20:34 +01:00
m-gues
df034f993f avant checkout application 2020-12-22 23:19:31 +01:00
m-gues
b0206bc0dd squelette servlet 2020-12-18 12:25:31 +01:00
m-gues
c13fb59f25 setup du projet 2020-12-16 12:46:05 +01:00
m-gues
79d50ea36d standard + connexion 2020-12-16 11:18:06 +01:00
m-gues
a1531eebb5 intégration standard+connexion presque terminée A UNE JFRAME QUI SE FERME PAS PRES 2020-12-14 18:30:02 +01:00
m-gues
f6b86d7aea début branche réunion 2020-12-09 11:04:43 +01:00
m-gues
5fd722d95b affichage liste utilisateurs non statique v2 2020-12-09 10:15:45 +01:00
m-gues
7b107a758f passage liste utilisateurs en privé : pas encore fonctionnel 2020-12-07 18:55:31 +01:00
Cavailles Kevin
7a4d831d56 package messages, changement comm 2020-12-05 18:48:01 +01:00
Cavailles Kevin
13ca8bb0c0 changements layout vue 2020-12-04 16:05:59 +01:00
Cavailles Kevin
94e1c6165b modif package comm + boutons connexion/deconnexion 2020-12-02 10:56:16 +01:00
Cavailles Kevin
539a0f0438 choix pseudo, modif et déconnexion + comm udp 2020-11-30 20:42:30 +01:00
Cavailles Kevin
1660e85004 Vue principale, liste utilisateurs, sélection, debut udp 2020-11-26 10:52:08 +01:00
65 changed files with 5644 additions and 18 deletions

View file

@ -0,0 +1,49 @@
package communication.filetransfer;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import observers.ObserverInputMessage;
public class FileTransferClient {
private int port;
private ArrayList<File> files;
private ObserverInputMessage obsInput;
/**
* 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.files = filesToSend;
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 {
for (File f : this.files) {
FileTransferSendingThread ftc = new FileTransferSendingThread(this.port, f, this.obsInput);
ftc.start();
ftc.join();
}
}
}

View file

@ -0,0 +1,102 @@
package communication.filetransfer;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import messages.Message;
import messages.MessageFichier;
import observers.ObserverInputMessage;
public class FileTransferReceivingThread extends Thread {
private SocketChannel sockTransfert;
private ObserverInputMessage obsInput;
/**
* 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.obsInput = o;
}
public void run() {
try {
int nbByteRead = 0;
// Buffer to receive a chunk of the file
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(
this.sockTransfert.socket().getInputStream());
int nbTotalBytesRead;
nbTotalBytesRead = 0;
Object o = inputFileInformation.readObject();
MessageFichier m = (MessageFichier) o;
String[] fileInfo = this.processFileInformation(m);
String filePath = FileTransferUtils.DOWNLOADS_RELATIVE_PATH + fileInfo[0];
long fileSize = Long.parseLong(fileInfo[1]);
// OutputStream to create the file if it does not exist
FileOutputStream fOutStream = new FileOutputStream(filePath);
// Channel to write the data received in the file
FileChannel fileWriter = fOutStream.getChannel();
while (nbTotalBytesRead < fileSize && (nbByteRead = this.sockTransfert.read(fileData)) > 0) {
fileData.flip();
fileWriter.write(fileData);
fileData.clear();
nbTotalBytesRead += nbByteRead;
}
fileWriter.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));
mUpdate.setSender("other");
this.obsInput.updateInput(this, mUpdate);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
this.sockTransfert.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 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) {
return m.getContenu().split(";");
}
}

View file

@ -0,0 +1,88 @@
package communication.filetransfer;
import java.io.File;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import messages.MauvaisTypeMessageException;
import messages.Message;
import messages.MessageFichier;
import messages.Message.TypeMessage;
import observers.ObserverInputMessage;
public class FileTransferSendingThread extends Thread {
private SocketChannel sockTransfert;
private File file;
private ObserverInputMessage obsInput;
/**
* 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();
SocketAddress addr = new InetSocketAddress(port);
sock.connect(addr);
this.sockTransfert = sock;
this.file = fileToSend;
this.obsInput = o;
}
public void run() {
try {
// Buffer to send a chunk of the file
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(
this.sockTransfert.socket().getOutputStream());
// Channel to read the data of the file
FileChannel fileReader = FileChannel.open(Paths.get(file.getPath()));
String str = file.getName() + ";" + file.getTotalSpace();
// Send file data (name + size);
outputFileInformation.writeObject(new MessageFichier(TypeMessage.FICHIER, str, ""));
while (fileReader.read(fileData) > 0) {
fileData.flip();
this.sockTransfert.write(fileData);
fileData.clear();
}
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);
mUpdate.setSender("Moi");
this.obsInput.updateInput(this, mUpdate);
} catch (IOException | MauvaisTypeMessageException e) {
e.printStackTrace();
} finally {
try {
this.sockTransfert.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

View file

@ -0,0 +1,57 @@
package communication.filetransfer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import observers.ObserverInputMessage;
public class FileTransferServer extends Thread {
private ServerSocketChannel sockFTListen;
private int nbFile;
private ObserverInputMessage obsInput;
/**
* 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.socket().bind(new InetSocketAddress(0));
this.nbFile = nbFile;
this.obsInput = o;
}
/**
* @return The port binded to the ServerSocketChannel.
*/
public int getPort() {
return this.sockFTListen.socket().getLocalPort();
}
@Override
public void run() {
try {
for (int i = 0; i < this.nbFile; i++) {
SocketChannel sock = this.sockFTListen.accept();
Thread ft = new FileTransferReceivingThread(sock, this.obsInput);
ft.start();
ft.join();
}
this.sockFTListen.close();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}

View file

@ -0,0 +1,150 @@
package communication.filetransfer;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import javax.imageio.ImageIO;
import messages.MessageFichier;
import messages.MauvaisTypeMessageException;
import messages.Message.TypeMessage;
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 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;
/**
* 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 {
String nameFile = file.getName();
String extension = processFileExtension(nameFile);
TypeMessage type;
String contenu;
if (IMAGE_EXTENSIONS.contains(extension)) {
type = TypeMessage.IMAGE;
BufferedImage img = ImageIO.read(file);
contenu = encodeImage(createThumbnail(img), extension);
} else {
type = TypeMessage.FICHIER;
contenu = nameFile;
}
try {
return new MessageFichier(type, contenu, extension);
} catch (MauvaisTypeMessageException e) {
e.printStackTrace();
}
return null;
}
/**
* @param fileName The name of the file (with its extension).
* @return The extension of the file.
*/
protected static String processFileExtension(String fileName) {
String extension = "";
int i = fileName.indexOf('.');
if (i >= 0 || i != -1) {
extension = fileName.substring(i + 1).toLowerCase();
}
return extension;
}
/**
* @param image A buffered image.
* @return A thumbnail of the image.
*/
private static BufferedImage createThumbnail(BufferedImage image) {
float w = image.getWidth();
float ratio = (w > 150) ? (150F / w) : 1;
BufferedImage scaled = scale(image, ratio);
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 {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ImageIO.write(img, extension, bos);
String imgString = Base64.getEncoder().encodeToString(bos.toByteArray());
bos.close();
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 {
byte[] imgData = Base64.getDecoder().decode(imageString);
InputStream is = new ByteArrayInputStream(imgData);
BufferedImage img = ImageIO.read(is);
is.close();
return img;
}
// Used to scale an image with a given ratio
private static BufferedImage scale(BufferedImage source, double ratio) {
int w = (int) (source.getWidth() * ratio);
int h = (int) (source.getHeight() * ratio);
BufferedImage bi = getCompatibleImage(w, h);
Graphics2D g2d = bi.createGraphics();
double xScale = (double) w / source.getWidth();
double yScale = (double) h / source.getHeight();
AffineTransform at = AffineTransform.getScaleInstance(xScale, yScale);
g2d.drawRenderedImage(source, at);
g2d.dispose();
return bi;
}
private static BufferedImage getCompatibleImage(int w, int h) {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
GraphicsConfiguration gc = gd.getDefaultConfiguration();
BufferedImage image = gc.createCompatibleImage(w, h);
return image;
}
/**
* Create the folder with the path "DOWNLOADS_RELATIVE_PATH" if it does not
* exist.
*/
public static void createDownloads() {
File downloads = new File(FileTransferUtils.DOWNLOADS_RELATIVE_PATH);
if (!downloads.exists()) {
downloads.mkdir();
}
}
}

View file

@ -0,0 +1,97 @@
package communication.tcp;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import observers.ObserverInputMessage;
import observers.ObserverSocketState;
import messages.Message;
public class TCPClient {
private Socket sockTCP;
private ObjectOutputStream output;
private ObjectInputStream input;
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 {
this.sockTCP = sockTCP;
this.output = new ObjectOutputStream(sockTCP.getOutputStream());
this.input = new ObjectInputStream(sockTCP.getInputStream());
this.inputThread = new TCPInputThread(this.input);
}
/**
* Start the thread that will continuously read from the socket.
*/
public void startInputThread() {
this.inputThread.start();
}
/**
* Send a message by writing it in the ObjectOutputStream of the socket
*
* @param message
* @throws IOException
*/
public void sendMessage(Message message) throws IOException {
this.output.writeObject(message);
}
/**
* Set the observer to notify when a message is received
*
* @param o The observer
*/
public void setObserverInputThread(ObserverInputMessage 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) {
this.inputThread.setObserverSocketState(o);
}
/**
* Method used when the session is over. Set all attributes' references to null,
* interrupt the inputThread and close the streams and the socket.
*/
public void destroyAll() {
try {
if (!this.sockTCP.isClosed()) {
this.inputThread.setObserverSocketState(null);
this.inputThread.interrupt();
this.input.close();
this.output.close();
this.sockTCP.close();
}
this.inputThread = null;
this.sockTCP = null;
this.output = null;
} catch (Exception e) {
e.printStackTrace();
}
}
}

View file

@ -0,0 +1,77 @@
package communication.tcp;
import java.io.IOException;
import java.io.ObjectInputStream;
import observers.ObserverInputMessage;
import observers.ObserverSocketState;
public class TCPInputThread extends Thread {
private ObjectInputStream input;
private boolean running;
private ObserverInputMessage obsInput;
private ObserverSocketState obsState;
/**
* Create the thread used to read the messages
*
* @param input The ObjectInputStream to read data from
*/
protected TCPInputThread(ObjectInputStream input) {
this.input = input;
this.running = true;
}
@Override
public void run() {
while (this.running) {
try {
Object o = this.input.readObject();
// Notify the observer a message was received
this.obsInput.updateInput(this, o);
} catch (IOException | ClassNotFoundException e) {
this.interrupt();
}
}
}
@Override
public void interrupt() {
// Stop the thread
this.running = false;
// Close the stream and the socket
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;
}
/**
* Set the observer to notify when a message is received
*
* @param o The observer
*/
protected void setObserverInputMessage(ObserverInputMessage o) {
this.obsInput = o;
}
/**
* Set the observer to notify when the session is cut/closed.
*
* @param o The observer
*/
protected void setObserverSocketState(ObserverSocketState o) {
this.obsState = o;
}
}

View file

@ -0,0 +1,52 @@
package communication.tcp;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import observers.ObserverInputMessage;
public class TCPServer extends Thread {
private ServerSocket sockListenTCP;
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 {
this.sockListenTCP = new ServerSocket(port, 50, InetAddress.getLocalHost());
}
@Override
public void run() {
Socket sockAccept;
while (true) {
try {
sockAccept = this.sockListenTCP.accept();
// Notify the observer of the new connexion
this.obsInput.updateInput(this, sockAccept);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 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

@ -0,0 +1,295 @@
package communication.udp;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import main.Utilisateur;
import messages.*;
import observers.ObserverUserList;
public class CommunicationUDP extends Thread {
private UDPClient client;
private UDPServer server;
private int portServer;
private ArrayList<Integer> portOthers;
private ArrayList<Utilisateur> users = new ArrayList<Utilisateur>();
private ObserverUserList obsList;
/**
* Create the object 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 {
this.portServer = portServer;
this.portOthers = this.getArrayListFromArray(portsOther);
this.server = new UDPServer(portServer, this);
this.server.start();
this.client = new UDPClient(portClient);
}
/**
* Create an ArrayList<Integer> from the int[] list of every servers' ports and
* remove the port of this application UDPServer.
*
* @param ports The UDPServer port numbers.
* @return An ArrayList<Integer> without the port of this UDPServer.
*/
private ArrayList<Integer> getArrayListFromArray(int ports[]) {
ArrayList<Integer> tmp = new ArrayList<Integer>();
for (int port : ports) {
tmp.add(port);
}
tmp.remove(Integer.valueOf(portServer));
return tmp;
}
/**
* 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 METHODS -------------- //
/**
* Add a new user to the userlist and notify the observer.
*
* @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();
}
/**
* Change the pseudo of an user and notify the observer if it exists in the
* userlist. Do nothing otherwise.
*
* @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();
}
}
/**
* 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) {
users.remove(index);
this.sendUpdate();
}
}
public void removeAllUsers() {
this.users.clear();
}
// -------------- 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) {
for (Utilisateur u : users) {
if (u.getId().equals(id)) {
return true;
}
}
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) {
for (Utilisateur u : users) {
if (u.getPseudo().toLowerCase().equals(pseudo)) {
return true;
}
}
return false;
}
// -------------- 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) {
for (int i = 0; i < users.size(); i++) {
if (users.get(i).getPseudo().equals(pseudo)) {
return users.get(i);
}
}
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) {
for (int i = 0; i < users.size(); i++) {
if (users.get(i).getId().equals(id)) {
return i;
}
}
return -1;
}
// -------------- SEND MESSAGES METHODS -------------- //
/**
* Send a message indicating this application's user is connected to every
* UDPServer.
*
* @throws UnknownHostException
* @throws IOException
*/
public void sendMessageConnecte() throws UnknownHostException, IOException {
try {
Message msgOut = new MessageSysteme(Message.TypeMessage.JE_SUIS_CONNECTE);
for (int port : this.portOthers) {
this.client.sendMessageUDP_local(msgOut, port);
}
} catch (MauvaisTypeMessageException e) {
e.printStackTrace();
}
}
/**
* Send a message containing this application's user's data to every UDPServer.
* This method is used to first add this user in the userlist or update this
* user's pseudo.
*
* @throws UnknownHostException
* @throws IOException
*/
public void sendMessageInfoPseudo() throws UnknownHostException, IOException {
Utilisateur self = Utilisateur.getSelf();
try {
Message msgOut = new MessageSysteme(Message.TypeMessage.INFO_PSEUDO, self.getPseudo(), self.getId(),
self.getPort());
for (int port : this.portOthers) {
this.client.sendMessageUDP_local(msgOut, port);
}
} catch (MauvaisTypeMessageException e) {
e.printStackTrace();
}
}
/**
* 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 {
Utilisateur self = Utilisateur.getSelf();
try {
Message msgOut = new MessageSysteme(Message.TypeMessage.INFO_PSEUDO, self.getPseudo(), self.getId(),
self.getPort());
this.client.sendMessageUDP_local(msgOut, portOther);
} catch (MauvaisTypeMessageException e) {
e.printStackTrace();
}
}
/**
* Send a message indicating this application's user is disconnected to every
* UDPServer.
*
* @throws UnknownHostException
* @throws IOException
*/
public void sendMessageDelete() throws UnknownHostException, IOException {
Utilisateur self = Utilisateur.getSelf();
try {
Message msgOut = new MessageSysteme(Message.TypeMessage.JE_SUIS_DECONNECTE, self.getPseudo(), self.getId(),
self.getPort());
for (int port : this.portOthers) {
this.client.sendMessageUDP_local(msgOut, port);
}
} catch (MauvaisTypeMessageException e) {
e.printStackTrace();
}
}
// -------------- OTHERS -------------- //
/**
* Notify the observer with the updated list
*/
private void sendUpdate() {
if (this.obsList != null) {
this.obsList.updateList(this, users);
}
}
}

View file

@ -0,0 +1,55 @@
package communication.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import messages.*;
class UDPClient {
private DatagramSocket sockUDP;
/**
* Create an 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 {
this.sockUDP = new DatagramSocket(port);
}
/**
* Send a message to the specified port on localhost.
*
* @param message
* @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);
}
}

View file

@ -0,0 +1,99 @@
package communication.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import main.Utilisateur;
import messages.*;
class UDPServer extends Thread {
private DatagramSocket sockUDP;
private CommunicationUDP commUDP;
private byte[] buffer;
private boolean running;
/**
* Create an 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 {
this.running = true;
this.commUDP = commUDP;
this.sockUDP = new DatagramSocket(port);
this.buffer = new byte[256];
}
@Override
public void run() {
while (this.running) {
try {
//When a datagram is received, converts its data in a Message
DatagramPacket inPacket = new DatagramPacket(buffer, buffer.length);
this.sockUDP.receive(inPacket);
String msgString = new String(inPacket.getData(), 0, inPacket.getLength());
Message msg = Message.stringToMessage(msgString);
//Depending on the type of the message
switch (msg.getTypeMessage()) {
case JE_SUIS_CONNECTE:
if (Utilisateur.getSelf() != null) {
int portClient = inPacket.getPort();
int portServer = portClient+1;
//Answer back with this application's user data
this.commUDP.sendMessageInfoPseudo(portServer);
}
break;
case INFO_PSEUDO:
MessageSysteme m = (MessageSysteme) msg;
//Update the userlist with the data received (Add the user or update it)
if (this.commUDP.containsUserFromID(m.getId())) {
this.commUDP.changePseudoUser(m.getId(), m.getPseudo(), inPacket.getAddress(), m.getPort());
} else {
this.commUDP.addUser(m.getId(), m.getPseudo(), inPacket.getAddress(), m.getPort());
}
break;
case JE_SUIS_DECONNECTE:
MessageSysteme m2 = (MessageSysteme) msg;
//Remove the user from the userlist
this.commUDP.removeUser(m2.getId(), m2.getPseudo(), inPacket.getAddress(), m2.getPort());
break;
//Do nothing
default:
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void interrupt() {
// Stop the thread
this.running = false;
// Close the stream and the socket
this.sockUDP.close();
this.buffer = null;
this.commUDP = null;
}
}

View file

@ -0,0 +1,179 @@
package connexion;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.UnknownHostException;
import java.sql.SQLException;
import communication.udp.CommunicationUDP;
import database.SQLiteManager;
import main.Utilisateur;
import standard.VueStandard;
public class ControleurConnexion implements ActionListener{
//Controller state : either DEBUT at initialization or ID_OK if the user has signed in
private enum Etat {DEBUT, ID_OK};
private VueConnexion vue;
private Etat etat;
private CommunicationUDP comUDP;
private int portTCP;
private String username;
private SQLiteManager sqlManager;
private VueStandard vueStd;
/**
* Create and initialize the object in charge of monitoring all actions depending on what the user do.
*
* @param vue : associated instance of VueConnexion
* @param numtest : on local mode, allows you to choose which port to use. Integer between 0 and 3
*
*/
public ControleurConnexion(VueConnexion vue, int numtest) {
this.vue = vue;
this.etat = Etat.DEBUT;
this.username = "";
this.sqlManager = new SQLiteManager(0);
this.vueStd = null;
int[] portServer = {2209, 2309, 2409, 2509, 3334};
try {
switch(numtest) {
case 0 :
this.comUDP = new CommunicationUDP(2208, 2209, portServer);
this.portTCP = 7010;
break;
case 1 :
this.comUDP = new CommunicationUDP(2308, 2309, portServer);
this.portTCP = 7020;
break;
case 2 :
this.comUDP = new CommunicationUDP(2408, 2409, portServer);
this.portTCP = 7030;
break;
case 3 :
this.comUDP = new CommunicationUDP(2508, 2509, portServer);
this.portTCP = 7040;
break;
default :
this.comUDP = new CommunicationUDP(2408, 2409, portServer);
this.portTCP = 7040;
}
} catch (IOException e) {
}
}
@Override
public void actionPerformed(ActionEvent e) {
String pseudo;
boolean inputOK = false;
if (this.etat == Etat.DEBUT) {
this.username = this.vue.getUsernameValue();
char[] password = this.vue.getPasswordValue();
try {
int res = this.sqlManager.checkPwd(this.username, password);
inputOK = (res == 1);
} catch (SQLException e2) {
}
if (inputOK) {
this.etat=Etat.ID_OK;
//Broadcast "JE_SUIS_CONNECTE" message and waits for the other devices' answers
try {
comUDP.sendMessageConnecte();
} catch (IOException e2) {
}
try {
Thread.sleep(2);
} catch (InterruptedException e1) {
}
//setup pseudo ask
this.vue.setConnexionInfo("");
this.vue.removePasswordPanel();
this.vue.setTextUsernameField("Veuillez entrer votre pseudonyme");
this.vue.resetUsernameField();
inputOK=false;
}
else {
this.vue.setConnexionInfo("Identifiant ou mot de passe invalide, veuillez réessayer");
this.vue.resetPasswordField();
}
}
else {
pseudo = vue.getUsernameValue();
//Search in the local list of active users id the chosen pseudo is already in use
inputOK = !this.comUDP.containsUserFromPseudo(pseudo);
if(pseudo.equals("")) {
this.vue.setConnexionInfo("Votre pseudonyme doit contenir au moins 1 caratère");
}else if (inputOK) {
//setup Utilisateur "self" static attribute
try {
Utilisateur.setSelf(this.username, pseudo, "localhost", this.portTCP);
} catch (UnknownHostException e2) {
}
//broadcast new pseudo
try {
this.comUDP.sendMessageInfoPseudo();
} catch (UnknownHostException e1) {
} catch (IOException e1) {
}
try {
this.resetView();
this.vue.setVisible(false);
this.setVueStandard();
} catch (IOException e1) {
}
}
else this.vue.setConnexionInfo("Ce nom est déjà utilisé, veuillez en choisir un autre");
}
}
// ----- SETTING & RESETTING VIEW ----- //
/**
* Create a new VueStandard instance and give it the hand.
*
*/
private void setVueStandard() throws IOException {
if(this.vueStd == null) {
this.vueStd = new VueStandard("Standard", this.comUDP, this.portTCP, this.sqlManager, this.vue);
}else {
this.vueStd.initControleur();
this.vueStd.setPseudoSelf();
this.vueStd.setVisible(true);
}
}
/**
* Restore the associated instance of VueConnexion to its initial state
*
*/
private void resetView() {
this.etat = Etat.DEBUT;
this.vue.addPasswordPanel();
this.vue.resetPasswordField();
this.vue.resetUsernameField();
this.vue.setTextUsernameField("Nom d'utilisateur");
this.vue.setConnexionInfo("");
}
}

View file

@ -0,0 +1,171 @@
package connexion;
//Import librairies
import java.awt.*;
import javax.swing.*;
import database.SQLiteManager;
import main.Vue;
public class VueConnexion extends Vue {
private static final long serialVersionUID = 1L;
//Graphical elements
private JButton boutonValider;
private JTextField inputUsername;
private JPasswordField inputPassword;
private JLabel labelUsername;
private JLabel labelPassword;
private JLabel connexionInfo;
private JPanel main;
private JPanel panelPassword;
//Controller
private ControleurConnexion controle;
/**
* Create and initialize the view SWING window that will be used during the connection phase.
* Doing so, it also creates the controller that will monitor all changes during the connection phase.
*
* @param numtest : to be passed down to the controller
*
*/
public VueConnexion(int numtest) {
super("Connexion");
controle = new ControleurConnexion(this, numtest);
//Window creation
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(400, 300);
this.setLocationRelativeTo(null);
//Adding graphical elements
ajouterElements();
//Setting default button
this.getRootPane().setDefaultButton(boutonValider);
this.inputUsername.setText(SQLiteManager.hardcodedNames[numtest]+numtest);
this.inputPassword.setText("aze1$"+SQLiteManager.hardcodedNames[numtest].charAt(0)+numtest);
//Display window
this.setVisible(true);
}
// ----- ADDING ELEMENTS TO AND REMOVING ELEMENTS FROM THE MAIN WINDOW ----- //
/**
* Add various graphical elements to the main window : used when initializing the window
*/
private void ajouterElements() {
//Create a panel
main = new JPanel(new GridLayout(4,1));
JPanel panelUsername = new JPanel(new GridLayout(1, 2));
this.panelPassword = new JPanel(new GridLayout(1, 2));
//Create various elements
this.connexionInfo = new JLabel("");
this.inputUsername = new JTextField();
this.inputUsername.setPreferredSize(new Dimension(100, 50));
this.labelUsername = new JLabel("Nom d'utilisateur");
this.labelUsername.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
this.inputPassword = new JPasswordField();
this.inputPassword.setPreferredSize(new Dimension(100, 50));
this.labelPassword = new JLabel("Mot de passe :");
this.labelPassword.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
boutonValider = new JButton("Valider");
//Make it so the controller is monitoring the button
boutonValider.addActionListener(controle);
panelUsername.add(this.labelUsername);
panelUsername.add(this.inputUsername);
this.panelPassword.add(this.labelPassword);
this.panelPassword.add(this.inputPassword);
main.add(connexionInfo);
main.add(panelUsername);
main.add(this.panelPassword);
main.add(boutonValider);
this.add(main);
}
protected void removePasswordPanel() {
this.main.remove(2);
}
protected void addPasswordPanel() {
this.main.add(this.panelPassword, 2);
}
//----- GETTERS -----//
/**
* Returns the current value of the field inputUsername
*
* @return current value of the field inputUsername as String
*/
protected String getUsernameValue() {
return this.inputUsername.getText();
}
/**
* Returns the current value of the field inputPassword
*
* @return current value of the field inputPassword as String
*/
protected char[] getPasswordValue() {
return this.inputPassword.getPassword();
}
//----- SETTERS -----//
/**
* Set a displayed message that will give the user information (for example if they entered a wrong password)
*
* @param text : message to display as String
*/
protected void setConnexionInfo(String text) {
this.connexionInfo.setText(text);
}
/**
* Set the label for the inputUsername fiel
*
* @param text : label to display as String
*/
protected void setTextUsernameField(String text) {
this.labelUsername.setText(text);
}
/**
* Empty the inputUsername text field
*/
protected void resetUsernameField() {
this.inputUsername.setText("");
}
/**
* Empty the inputPassword text field
*/
protected void resetPasswordField() {
this.inputPassword.setText("");
}
}

View file

@ -0,0 +1,117 @@
package database;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
class SQLiteCreateTables {
/**
* Create the user table if it does not exist in the database.
* An user is characterized by :
* - an unique username,
* - a password salt,
* - a database_key salt,
* - a password hash encrypted by the database_key,
* - the encrypted database_key,
* - the initialization vector used to encrypt the database key.
*
* @param connec The opened connection to the database.
* @throws SQLException
*/
protected static void createTableUser(Connection connec) throws SQLException {
String createTableUser = "CREATE TABLE IF NOT EXISTS user (\r\n"
+ " id INTEGER PRIMARY KEY AUTOINCREMENT,\r\n"
+ " username VARCHAR (50) NOT NULL\r\n"
+ " UNIQUE ON CONFLICT ROLLBACK,\r\n"
+ " pwd_salt BLOB,\r\n"
+ " db_datakey_salt BLOB,\r\n"
+ " encrypted_pwd_hashsalt BLOB,\r\n"
+ " encrypted_db_datakey BLOB,\r\n"
+ " iv_datakey BLOB\r\n" + ");";
Statement stmt = connec.createStatement();
stmt.execute(createTableUser);
}
/**
* Create the conversation table if it does not exist in the database.
* A conversation is characterized by :
* - the id of the user who sends the messages,
* - the id of the user who receives the messages,
* - an initialization vector used to encrypt the conversation's messages.
*
* During an session between two users, two conversations are created.
*
* @param connec The opened connection to the database.
* @throws SQLException
*/
protected static void createTableConversation(Connection connec) throws SQLException {
String createTableConversation = "CREATE TABLE IF NOT EXISTS conversation (\r\n"
+ " id_conversation INTEGER PRIMARY KEY AUTOINCREMENT,\r\n"
+ " id_sender INTEGER REFERENCES user (id) \r\n" + " NOT NULL,\r\n"
+ " id_receiver INTEGER REFERENCES user (id) \r\n" + " NOT NULL,\r\n"
+ " iv_conversation BLOB NOT NULL" + ");";
Statement stmt = connec.createStatement();
stmt.execute(createTableConversation);
}
/**
* Create the message table if it does not exist in the database.
* A message is characterized by :
* - the id of the conversation it belongs,
* - the id of its type,
* - its content,
* - the date when it was emitted,
* - its extension if it is a file (text or image).
*
* @param connec The opened connection to the database.
* @throws SQLException
*/
protected static void createTableMessage(Connection connec) throws SQLException {
String createTableMessage = "CREATE TABLE IF NOT EXISTS message (\r\n"
+ " id_message INTEGER PRIMARY KEY AUTOINCREMENT,\r\n"
+ " id_conversation INTEGER REFERENCES conversation (id_conversation) \r\n"
+ " NOT NULL,\r\n"
+ " id_type INTEGER REFERENCES type (id_type) \r\n"
+ " NOT NULL,\r\n"
+ " content BLOB,\r\n"
+ " date INTEGER NOT NULL,\r\n"
+ " extension VARCHAR (20) \r\n" + ");\r\n";
Statement stmt = connec.createStatement();
stmt.execute(createTableMessage);
}
/**
* Create the (message) type table if it does not exist and insert the different
* types of message in the database.
* A type is characterized by :
* - a label.
*
* This table only exists because the type "enumeration" does not exist in SQLite.
* It is a static table that contains the different types of message stored in the database.
*
* @param connec The opened connection to the database.
* @throws SQLException
*/
protected static void createTableType(Connection connec) throws SQLException {
String createTableType = "CREATE TABLE IF NOT EXISTS type (\r\n"
+ " id_type INTEGER PRIMARY KEY,\r\n"
+ " label VARCHAR (20) NOT NULL\r\n" + ");";
Statement stmt = connec.createStatement();
stmt.execute(createTableType);
String typeText = "INSERT OR IGNORE INTO type (id_type, label) " + "VALUES (0, 'text');";
String typeFile = "INSERT OR IGNORE INTO type (id_type, label) " + "VALUES (1, 'file');";
String typeImage = "INSERT OR IGNORE INTO type (id_type, label) " + "VALUES (2, 'image');";
stmt.execute(typeText);
stmt.execute(typeFile);
stmt.execute(typeImage);
}
}

View file

@ -0,0 +1,158 @@
package database;
import java.util.Base64;
import java.util.Random;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.*;
import java.security.spec.*;
class SQLiteEncryption {
private static final Random RANDOM = new SecureRandom();
private static final int ITERATIONS = 10000;
private static final int KEY_LENGTH = 256;
protected static final String encryptAlgorithm = "AES/CBC/PKCS5Padding";
/**
* Return a 24 bytes salt.
*
* @return The salt in a byte array.
*/
protected static byte[] getNextSalt() {
byte[] salt = new byte[24];
RANDOM.nextBytes(salt);
return salt;
}
/**
* Return the hash of the given password with the given salt.
*
* @param password
* @param salt
* @return The hash in a byte array.
*/
protected static byte[] hash(char[] password, byte[] salt) {
return SQLiteEncryption.getKey(password, salt).getEncoded();
}
/**
* Return a secret key generated with the given password and salt.
*
* @param password
* @param salt
* @return The secret key.
*/
protected static SecretKey getKey(char[] password, byte[] salt) {
PBEKeySpec saltpwd = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH);
try {
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
SecretKey tmp = skf.generateSecret(saltpwd);
SecretKey key = new SecretKeySpec(tmp.getEncoded(), "AES");
return key;
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
e.printStackTrace();
} finally {
saltpwd.clearPassword();
}
return null;
}
/**
* Return a 16 bytes Initialization vector.
*
* @return The Initialization vector.
*/
protected static IvParameterSpec generateIv() {
byte[] iv = new byte[16];
RANDOM.nextBytes(iv);
return new IvParameterSpec(iv);
}
/**
* Encrypt the given input (byte array) with the given algorithm, secretKey and
* initialization vector.
*
*
* @param algorithm
* @param input
* @param key
* @param iv
* @return The encrypted input in a byte array.
*
* @throws NoSuchAlgorithmException
* @throws NoSuchPaddingException
* @throws InvalidKeyException
* @throws InvalidAlgorithmParameterException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
*/
protected static byte[] encrypt(String algorithm, byte[] input, SecretKey key, IvParameterSpec iv)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] cipherText = cipher.doFinal(input);
return Base64.getEncoder().encode(cipherText);
}
/**
* Decrypt the given input (byte array) with the given algorithm, secretKey and
* initialization vector.
*
* @param algorithm
* @param input
* @param key
* @param iv
* @return The decrypted input in a byte array.
*
* @throws NoSuchAlgorithmException
* @throws NoSuchPaddingException
* @throws InvalidKeyException
* @throws InvalidAlgorithmParameterException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
*/
protected static byte[] decryptByte(String algorithm, byte[] input, SecretKey key, IvParameterSpec iv)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] plainText = cipher.doFinal(Base64.getDecoder().decode(input));
return plainText;
}
/**
* Decrypt the given input (byte array) with the given algorithm, secretKey and
* initialization vector.
*
* @param algorithm
* @param input
* @param key
* @param iv
* @return The decrypted input as a String.
*
* @throws InvalidKeyException
* @throws NoSuchAlgorithmException
* @throws NoSuchPaddingException
* @throws InvalidAlgorithmParameterException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
*/
protected static String decryptString(String algorithm, byte[] input, SecretKey key, IvParameterSpec iv)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
return new String(SQLiteEncryption.decryptByte(algorithm, input, key, iv));
}
protected static byte[] keyToByte(SecretKey key) {
return Base64.getEncoder().encode(key.getEncoded());
}
protected static SecretKey byteToKey(byte[] encodedKey) {
byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
return new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
}
}

View file

@ -0,0 +1,760 @@
package database;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import main.Utilisateur;
import messages.MauvaisTypeMessageException;
import messages.Message;
import messages.MessageTexte;
import messages.Message.TypeMessage;
import messages.MessageFichier;
public class SQLiteManager {
private static final String DATABASE_RELATIVE_PATH = "../database";
public static String[] hardcodedNames = { "Olivia", "Liam", "Benjamin", "Sophia", "Charlotte", "Noah", "Elijah",
"Isabella", "Oliver", "Emma", "William", "Amelia", "Evelyn", "James", "Mia", "Ava", "Lucas", "Mason",
"Ethan", "Harper" };
private Connection connec;
private int numDatabase;
private SecretKey dbDataKey;
/**
* Create the object that will interact with the database. Each time a
* SQLiteManager is created, it creates the tables for the application to work
* if they do not exist already. In the context of a local usage of the
* application, this constructor takes a number that is used to manage different
* database's files (see openConnection() ).
*
* @param numDatabase
*/
public SQLiteManager(int numDatabase) {
this.numDatabase = numDatabase;
this.openConnection();
try {
SQLiteCreateTables.createTableUser(this.connec);
SQLiteCreateTables.createTableConversation(this.connec);
SQLiteCreateTables.createTableType(this.connec);
SQLiteCreateTables.createTableMessage(this.connec);
} catch (SQLException e) {
this.closeConnection();
e.printStackTrace();
}
this.closeConnection();
}
// -------------- CONNECTION METHODS -------------- //
/**
* Open a connection to the file DATABASE_RELATIVE_PATH+this.numDatabase+".db"
*/
private void openConnection() {
String url = "jdbc:sqlite:" + DATABASE_RELATIVE_PATH + this.numDatabase + ".db";
try {
this.connec = DriverManager.getConnection(url);
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
/**
* Close this object connection, if it exists
*/
private void closeConnection() {
try {
if (this.connec != null) {
this.connec.close();
}
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
// -------------- INSERT METHODS -------------- //
/**
* Insert a collection of message in the database. They all correspond to the
* conversation between the sender and the receiver. The users and conversations
* are inserted in the database if they do not exist when this method is called.
*
* @param messages
* @param usernameSender
* @param usernameReceiver
* @return The number of messages inserted
* @throws SQLException
*/
public int insertAllMessages(ArrayList<Message> messages, String usernameSender, String usernameReceiver)
throws SQLException {
int nbRows = 0;
this.openConnection();
int idSender = this.getIDUser(usernameSender);
int idReceiver = this.getIDUser(usernameReceiver);
if (idSender == -1) {
this.insertUser(usernameSender);
idSender = this.getIDUser(usernameSender);
}
if (idReceiver == -1) {
this.insertUser(usernameReceiver);
idReceiver = this.getIDUser(usernameReceiver);
}
int idConversation = getIDConversation(idSender, idReceiver);
if (idConversation == -1) {
this.insertConversation(idSender, idReceiver);
idConversation = getIDConversation(idSender, idReceiver);
}
IvParameterSpec ivConversation = this.getIvConversation(idConversation);
// Disable autocommit to efficiently insert all the messages.
this.connec.setAutoCommit(false);
for (Message m : messages) {
try {
nbRows += this.insertMessage(idConversation, m, ivConversation);
} catch (SQLException e) {
e.printStackTrace();
this.connec.rollback();
}
}
// Commit once all the messages are inserted
this.connec.commit();
this.closeConnection();
return nbRows;
}
/**
* Insert a message corresponding to a given conversation (respresented by its
* id). The message content is first encrypted with the database key and the
* given iv.
*
* @param idConversation
* @param m
* @param iv
* @return The number of rows inserted (it should be 1).
* @throws SQLException
*/
private int insertMessage(int idConversation, Message m, IvParameterSpec iv) throws SQLException {
String dateMessage = m.getDateMessage();
String extension = this.processExtension(m);
String type = this.processMessageType(m);
int idType = this.getIDType(type);
byte[] content = null;
try {
content = this.stringToBytesContent(m, iv);
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
}
String insertMessageRequest = "INSERT INTO message(id_conversation, id_type, content, date, extension) "
+ "VALUES (?, ?, ?, ?, ?);";
PreparedStatement prepStmt = this.connec.prepareStatement(insertMessageRequest);
prepStmt.setInt(1, idConversation);
prepStmt.setInt(2, idType);
prepStmt.setBytes(3, content);
prepStmt.setString(4, dateMessage);
prepStmt.setString(5, extension);
int nbRows = prepStmt.executeUpdate();
return nbRows;
}
/**
* Insert an user in the database with only its username.
*
* @param username
* @throws SQLException
*/
private void insertUser(String username) throws SQLException {
String insertUserRequest = "INSERT INTO user (username) " + "VALUES (?);";
PreparedStatement prepStmt = this.connec.prepareStatement(insertUserRequest);
prepStmt.setString(1, username);
prepStmt.executeUpdate();
}
/**
* Insert the two conversations corresponding to a session between two users
* (they both can be sender and receiver).
*
* @param idUser1
* @param idUser2
* @throws SQLException
*/
private void insertConversation(int idUser1, int idUser2) throws SQLException {
String insertConversationRequest = "INSERT INTO conversation (id_sender, id_receiver, iv_conversation) "
+ "VALUES " + "(?, ?, ?)," + "(?, ?, ?);";
byte[] ivConversation = SQLiteEncryption.generateIv().getIV();
PreparedStatement prepStmt = this.connec.prepareStatement(insertConversationRequest);
prepStmt.setInt(1, idUser1);
prepStmt.setInt(2, idUser2);
prepStmt.setBytes(3, ivConversation);
prepStmt.setInt(4, idUser2);
prepStmt.setInt(5, idUser1);
prepStmt.setBytes(6, ivConversation);
prepStmt.executeUpdate();
}
// -------------- GET METHODS -------------- //
/**
* Get the message record between two users. In context, it only needs the
* username and the id of the other user since we can get this application's
* user's data with Utilisateur.getSelf().
*
* @param usernameOther
* @param pseudoOther
* @return the messages previously exchanged in chronological order or null if
* there is none.
* @throws SQLException
*/
public ArrayList<Message> getMessageRecord(String usernameOther, String pseudoOther) throws SQLException {
this.openConnection();
ArrayList<Message> messages = new ArrayList<Message>();
String usernameSelf = Utilisateur.getSelf().getId();
// Get the ids from the usernames
int idSelf = this.getIDUser(usernameSelf);
int idOther = this.getIDUser(usernameOther);
// Get the two conversations corresponding to the exchanges between the two
// users
int idConversationSelf = this.getIDConversation(idSelf, idOther);
int idConversationOther = this.getIDConversation(idOther, idSelf);
IvParameterSpec ivConversation = this.getIvConversation(idConversationSelf);
// Get all the messages
String getHistoriqueRequest = "SELECT id_conversation, id_type, content, date, extension " + "FROM message "
+ "WHERE id_conversation IN (?,?) " + "ORDER by date";
PreparedStatement prepStmt = this.connec.prepareStatement(getHistoriqueRequest);
prepStmt.setInt(1, idConversationSelf);
prepStmt.setInt(2, idConversationOther);
ResultSet res = prepStmt.executeQuery();
// Process the messages one by one
// Create the appropriate message object depending on the type and
// sender/receiver
// and add the message in the list
while (res.next()) {
int idType = res.getInt("id_type");
String type = this.getType(idType);
String content = null;
try {
// Decrypt the message's content with the database key and the conversation's
// iv.
content = this.bytesToStringContent(res.getBytes("content"), ivConversation);
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException
| SQLException e1) {
}
Message message = null;
String extension;
if (!type.equals("")) {
try {
switch (type) {
case "text":
message = new MessageTexte(TypeMessage.TEXTE, content);
break;
case "file":
extension = res.getString("extension");
message = new MessageFichier(TypeMessage.FICHIER, content, extension);
break;
default:
extension = res.getString("extension");
message = new MessageFichier(TypeMessage.IMAGE, content, extension);
}
} catch (MauvaisTypeMessageException e) {
e.printStackTrace();
}
}
if (res.getInt("id_conversation") == idConversationSelf) {
message.setSender("Moi");
} else {
message.setSender(pseudoOther);
}
message.setDateMessage(res.getString("date"));
if (content != null) {
messages.add(message);
}
}
this.closeConnection();
return messages;
}
/**
* Return the id of the user with the given username if it exists in the
* database.
*
* @param username
* @return The id of the user or -1 if he does not exist in the database.
* @throws SQLException
*/
private int getIDUser(String username) throws SQLException {
String getIDRequest = "SELECT id " + " FROM user" + " WHERE username = ? ;";
PreparedStatement prepStmt = this.connec.prepareStatement(getIDRequest);
prepStmt.setString(1, username);
ResultSet res = prepStmt.executeQuery();
if (res.next()) {
return res.getInt("id");
}
return -1;
}
/**
* Get the id of the conversation between two users (represented by their ids).
*
* @param idSender
* @param idReceiver
* @return The id of the conversation or -1 if the conversation does not exists
* in the database.
* @throws SQLException
*/
private int getIDConversation(int idSender, int idReceiver) throws SQLException {
String getIDRequest = "SELECT id_conversation " + "FROM conversation " + "WHERE id_sender = ? "
+ "AND id_receiver = ? ;";
PreparedStatement prepStmt = this.connec.prepareStatement(getIDRequest);
prepStmt.setInt(1, idSender);
prepStmt.setInt(2, idReceiver);
ResultSet res = prepStmt.executeQuery();
if (res.next()) {
return res.getInt("id_conversation");
}
return -1;
}
/**
* Get the initialization vector for a given conversation (represented by its
* id).
*
* @param idConversation
* @return The iv corresponding to the conversation or null if the conversation
* does not exist in the database.
* @throws SQLException
*/
private IvParameterSpec getIvConversation(int idConversation) throws SQLException {
String getIvRequest = "SELECT iv_conversation " + "FROM conversation " + "WHERE id_conversation = ?;";
PreparedStatement prepStmt = this.connec.prepareStatement(getIvRequest);
prepStmt.setInt(1, idConversation);
ResultSet res = prepStmt.executeQuery();
if (res.next()) {
return new IvParameterSpec(res.getBytes("iv_conversation"));
}
return null;
}
/**
* Get the id of the message type corresponding to the given label.
*
* @param label
* @return The id of the message type or -1 if it does not exist in the
* database.
* @throws SQLException
*/
private int getIDType(String label) throws SQLException {
String getIDRequest = "SELECT id_type FROM type WHERE label = ?;";
PreparedStatement prepStmt = this.connec.prepareStatement(getIDRequest);
prepStmt.setString(1, label);
ResultSet res = prepStmt.executeQuery();
if (res.next()) {
return res.getInt("id_type");
}
return -1;
}
/**
* Get the label of the message type corresponding to the given id.
*
* @param idType
* @return The label of the message type or "" if it does not exist in the
* database.
* @throws SQLException
*/
private String getType(int idType) throws SQLException {
String getTypeRequest = "SELECT label FROM type WHERE id_type = ?;";
PreparedStatement prepStmt = this.connec.prepareStatement(getTypeRequest);
prepStmt.setInt(1, idType);
ResultSet res = prepStmt.executeQuery();
if (res.next()) {
return res.getString("label");
}
return "";
}
// -------------- PROCESSING MESSAGE METHODS -------------- //
/**
* Convert a message in bytes. First get the content of the message. Then
* encrypt it with the database_key and the given iv (initialization vector).
*
* @param m
* @param iv
* @return The bytes of the encrypted message's content.
*
* @throws InvalidKeyException
* @throws NoSuchAlgorithmException
* @throws NoSuchPaddingException
* @throws InvalidAlgorithmParameterException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
*/
private byte[] stringToBytesContent(Message m, IvParameterSpec iv)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
String content;
if (m.getTypeMessage() == TypeMessage.TEXTE) {
MessageTexte messageTxt = (MessageTexte) m;
content = messageTxt.getContenu();
} else {
MessageFichier messageFichier = (MessageFichier) m;
content = messageFichier.getContenu();
}
byte[] encryptedContent = SQLiteEncryption.encrypt(SQLiteEncryption.encryptAlgorithm, content.getBytes(),
this.dbDataKey, iv);
return encryptedContent;
}
/**
* Convert bytes in a String. In this context, the bytes correspond to the
* encrypted content of a message.
*
* @param encryptedContent
* @param iv
* @return The String corresponding to the decrypted bytes.
*
* @throws InvalidKeyException
* @throws NoSuchAlgorithmException
* @throws NoSuchPaddingException
* @throws InvalidAlgorithmParameterException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
*/
private String bytesToStringContent(byte[] encryptedContent, IvParameterSpec iv)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
return SQLiteEncryption.decryptString(SQLiteEncryption.encryptAlgorithm, encryptedContent, this.dbDataKey, iv);
}
/**
* Process the type of a message
*
* @param m
* @return The type of the message
*/
private String processMessageType(Message m) {
switch (m.getTypeMessage()) {
case TEXTE:
return "text";
case FICHIER:
return "file";
case IMAGE:
return "image";
default:
return "";
}
}
/**
* Process the extension of a message
*
* @param m
* @return The extension of the message. null if it is a simple text message.
*/
private String processExtension(Message m) {
if (m.getTypeMessage() == TypeMessage.TEXTE) {
return null;
} else {
MessageFichier mFile = (MessageFichier) m;
return mFile.getExtension();
}
}
// -------------- USER SECURITY RELATED METHODS -------------- //
/**
* Creates a new user in the database from a given username and password. The
* username is stored in plain text. An encrypted hash of the password is
* stored. The key used to encrypt the password's hash is itself encrypted then
* stored. Every other useful data to decrypt the key and compare the password's
* hash is stored as plaintext.
*
* Fail if a user with the given username already exists in the database.
*
* @param username
* @param password
*/
public void createNewUserEncrypt(String username, String password) {
String algo = SQLiteEncryption.encryptAlgorithm;
KeyGenerator keyGen = null;
try {
keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
byte[] passwordSalt = SQLiteEncryption.getNextSalt();
byte[] dbDataKeySalt = SQLiteEncryption.getNextSalt();
SecretKey dbDataKey = keyGen.generateKey();
SecretKey dbDataEncryptKey = SQLiteEncryption.getKey(password.toCharArray(), dbDataKeySalt);
IvParameterSpec ivDbDataKey = SQLiteEncryption.generateIv();
byte[] passwordHash = SQLiteEncryption.hash(password.toCharArray(), passwordSalt);
byte[] dbDataKeyEncrypted = null;
byte[] encryptedPasswordHash = null;
try {
dbDataKeyEncrypted = SQLiteEncryption.encrypt(algo, SQLiteEncryption.keyToByte(dbDataKey), dbDataEncryptKey,
ivDbDataKey);
encryptedPasswordHash = SQLiteEncryption.encrypt(algo, passwordHash, dbDataKey, ivDbDataKey);
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
}
this.openConnection();
String createUserRequest = "INSERT INTO user(username, pwd_salt, db_datakey_salt, encrypted_pwd_hashsalt, encrypted_db_datakey, iv_datakey) "
+ "VALUES (?, ?, ?, ?, ?, ?); ";
PreparedStatement prepStmt = null;
try {
prepStmt = this.connec.prepareStatement(createUserRequest);
prepStmt.setString(1, username);
prepStmt.setBytes(2, passwordSalt);
prepStmt.setBytes(3, dbDataKeySalt);
prepStmt.setBytes(4, encryptedPasswordHash);
prepStmt.setBytes(5, dbDataKeyEncrypted);
prepStmt.setBytes(6, ivDbDataKey.getIV());
} catch (SQLException e) {
e.printStackTrace();
}
try {
prepStmt.executeUpdate();
System.out.println("Utilisateur crée");
} catch (SQLException e) {
System.out.println("Nom d'utilisateur déjà pris");
}
this.closeConnection();
}
/**
* Check if a given password has the same hash (= they're the same) as the one
* stored in the database for a given username.
*
* @param username
* @param password
* @return -1 if user do not exists, 0 if password is incorrect, 1 if password
* is correct
* @throws SQLException
*/
public int checkPwd(String username, char[] password) throws SQLException {
this.openConnection();
String selectUserDataRequest = "SELECT pwd_salt, db_datakey_salt, encrypted_pwd_hashsalt, encrypted_db_datakey, iv_datakey "
+ "FROM user " + "WHERE username = ?;";
PreparedStatement prepStmt;
prepStmt = this.connec.prepareStatement(selectUserDataRequest);
prepStmt.setString(1, username);
ResultSet res = prepStmt.executeQuery();
if (!res.next()) {
return -1;
}
byte[] passwordSalt = res.getBytes("pwd_salt");
if (passwordSalt == null) {
return 0;
}
byte[] dbDataKeySalt = res.getBytes("db_datakey_salt");
SecretKey dbDataEncryptKey = SQLiteEncryption.getKey(password, dbDataKeySalt);
byte[] ivBytes = res.getBytes("iv_datakey");
if (ivBytes == null) {
return 0;
}
IvParameterSpec iv = new IvParameterSpec(ivBytes);
byte[] encryptedDbDataKey = res.getBytes("encrypted_db_datakey");
SecretKey dbDataKey = null;
try {
dbDataKey = SQLiteEncryption.byteToKey(SQLiteEncryption.decryptByte(SQLiteEncryption.encryptAlgorithm,
encryptedDbDataKey, dbDataEncryptKey, iv));
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
}
byte[] encryptedPasswordHash = res.getBytes("encrypted_pwd_hashsalt");
byte[] passwordHash = SQLiteEncryption.hash(password, passwordSalt);
this.closeConnection();
boolean checkHash = this.checkHashPwd(passwordHash, encryptedPasswordHash, dbDataKey, iv);
if (checkHash) {
// Set the database key to be used in future encryptions if the given password is
// correct.
this.dbDataKey = dbDataKey;
return 1;
}
return 0;
}
/**
* Check if two given hash are the same once the second one has been decrypted
* with the given key and iv.
*
* @param passwordHash
* @param encryptedPasswordHash
* @param dbDataKey
* @param iv
* @return true if the first hash is equal to the second one once decrypted,
* false otherwise.
*
*/
private boolean checkHashPwd(byte[] passwordHash, byte[] encryptedPasswordHash, SecretKey dbDataKey,
IvParameterSpec iv) {
byte[] expectedHash = "".getBytes();
try {
expectedHash = SQLiteEncryption.decryptByte(SQLiteEncryption.encryptAlgorithm, encryptedPasswordHash,
dbDataKey, iv);
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
}
if (passwordHash.length != expectedHash.length)
return false;
for (int i = 0; i < passwordHash.length; i++) {
if (passwordHash[i] != expectedHash[i])
return false;
}
return true;
}
// Main to create 20 users in the database with the given number
public static void main(String[] args) {
String[] hardcodedNames = { "Olivia", "Liam", "Benjamin", "Sophia", "Charlotte", "Noah", "Elijah", "Isabella",
"Oliver", "Emma", "William", "Amelia", "Evelyn", "James", "Mia", "Ava", "Lucas", "Mason", "Ethan",
"Harper" };
String pwdPrefix = "aze1$";
SQLiteManager sqlManager = new SQLiteManager(0);
for (int i = 0; i < hardcodedNames.length; i++) {
sqlManager.createNewUserEncrypt(hardcodedNames[i] + i, pwdPrefix + hardcodedNames[i].charAt(0) + i);
}
}
}

37
POO/src/main/Main.java Normal file
View file

@ -0,0 +1,37 @@
package main;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import communication.filetransfer.FileTransferUtils;
import connexion.VueConnexion;
public class Main extends JPanel{
/**
*
*/
private static final long serialVersionUID = 1L;
public static void main(String[] args) {
try {
for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (Exception e) {
// If Nimbus is not available, you can set the GUI to another look and feel.
}
FileTransferUtils.createDownloads();
new VueConnexion(Integer.parseInt(args[0]));
}
}

View file

@ -0,0 +1,115 @@
package main;
import java.io.Serializable;
import java.net.*;
public class Utilisateur implements Serializable{
private static final long serialVersionUID = 1L;
private String id;
private String pseudo;
private InetAddress ip;
private int port;
//Represents the user that is currently using the application
private static Utilisateur self;
/**
* Create and initialize an object representing an user
*
* @param id : user id as String
* @param pseudo : name under which other users can see this user as String
* @param ip : ip of the device this user is currently using as InetAddress
* @param port : on local mode, port used for the TCP listen socket as int
*
*/
public Utilisateur(String id, String pseudo, InetAddress ip, int port) throws UnknownHostException {
this.id = id;
this.pseudo = pseudo;
this.ip = ip;
this.port = port;
}
// ----- GETTERS ----- //
/**
* Returns user id as String
*
* @return user id as String
*/
public String getId() {
return id;
}
/**
* Returns user pseudo as String
*
* @return user pseudo as String
*/
public String getPseudo() {
return pseudo;
}
/**
* Returns user device's ip as String
*
* @return user device's ip as String
*/
public InetAddress getIp() {
return ip;
}
/**
* Returns the port the user uses for their TCP listen socket as int
*
* @return TCP listen socket port as int
*/
public int getPort() {
return port;
}
/**
* Returns the user currently using this instance of the application as Utilisateur
*
* @return current user as Utilisateur
*/
public static Utilisateur getSelf() {
return Utilisateur.self;
}
// ----- SETTERS ----- //
/**
* Change the pseudo used by an user
*
* @param pseudo : new pseudo as String
*/
public void setPseudo(String pseudo) {
this.pseudo = pseudo;
}
/**
* Sets the self static attribute with a new Utilisateur
*
* @param id : user id as String
* @param pseudo : name under which other users can see this user as String
* @param ip : ip of the device this user is currently using as InetAddress
* @param port : on local mode, port used for the TCP listen socket as int
*/
public static void setSelf(String id, String pseudo, String host, int port) throws UnknownHostException {
if(Utilisateur.self == null) {
Utilisateur.self = new Utilisateur(id, pseudo, InetAddress.getByName(host), port);
}
}
/**
* Sets the self static attribute with null
*/
public static void resetSelf() {
Utilisateur.self = null;
}
}

22
POO/src/main/Vue.java Normal file
View file

@ -0,0 +1,22 @@
package main;
import javax.swing.JFrame;
// General class from which all VueX class derivate
public class Vue extends JFrame{
private static final long serialVersionUID = 1L;
public Vue(String title) {
super(title);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public void reduireAgent() {}
public void close() {
this.dispose();
}
}

View file

@ -0,0 +1,8 @@
package messages;
public class MauvaisTypeMessageException extends Exception {
private static final long serialVersionUID = 1L;
}

View file

@ -0,0 +1,130 @@
package messages;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public abstract class Message implements Serializable {
public enum TypeMessage {JE_SUIS_CONNECTE, JE_SUIS_DECONNECTE, INFO_PSEUDO, TEXTE, IMAGE, FICHIER, FICHIER_INIT, FICHIER_ANSWER}
protected TypeMessage type;
private String dateMessage;
private String sender;
private static final long serialVersionUID = 1L;
// ------- GETTERS ------ //
/**
* Returns the current date and time as a string using DateTimeFormatter and LocalDateTime
*
* @return date and time as a String
*/
public static String getDateAndTime() {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
LocalDateTime now = LocalDateTime.now();
return dtf.format(now);
}
/**
* Returns the type of the message
*
* @return message type as TypeMessage
*/
public TypeMessage getTypeMessage() {
return this.type;
}
/**
* Returns the date and time to which the message was timestamped
*
* @return date and time of timestamp as String
*/
public String getDateMessage() {
return this.dateMessage;
}
/**
* Returns the sender of the message (used in the database)
*
* @return sender of message as String
*/
public String getSender() {
return this.sender ;
}
// ------ SETTERS ------ //
/**
* Set the date of the message to a specific timestamp
*
* @param timestamp as (formatted) String
*/
public void setDateMessage(String dateMessage) {
this.dateMessage = dateMessage;
}
/**
* Set the sender of the message to a specified string
*
* @param sender pseudo as String
*/
public void setSender(String sender) {
this.sender = sender;
}
// ----- MESSAGE-STRING CONVERSION METHODS -------//
/**
* Returns a string representing the formatted list of attributes
*
*@return attributes as a String
*/
protected abstract String attributsToString();
/**
* Returns the message as a formatted string
*
*@return message as a String
*/
public String toString() {
return this.type+"###"+this.attributsToString();
}
/**
* Static method. Returns a message obtainer by parsing a given string
*
*@param String representing a message
*@return Message
*/
public static Message stringToMessage(String messageString) {
try {
String[] parts = messageString.split("###");
switch (parts[0]) {
case "JE_SUIS_CONNECTE" :
return new MessageSysteme(TypeMessage.JE_SUIS_CONNECTE);
case "JE_SUIS_DECONNECTE" :
return new MessageSysteme(TypeMessage.JE_SUIS_DECONNECTE, parts[1], parts[2], Integer.parseInt(parts[3]) );
case "INFO_PSEUDO" :
return new MessageSysteme(TypeMessage.INFO_PSEUDO, parts[1], parts[2], Integer.parseInt(parts[3]) );
case "TEXTE" :
return new MessageTexte(TypeMessage.TEXTE, parts[1]);
case "IMAGE" :
return new MessageFichier(TypeMessage.IMAGE, parts[1], parts[2]);
case "FICHIER" :
return new MessageFichier(TypeMessage.FICHIER, parts[1], parts[2]);
}
} catch (MauvaisTypeMessageException e) {}
return null;
}
}

View file

@ -0,0 +1,82 @@
package messages;
public class MessageFichier extends Message {
private static final long serialVersionUID = 1L;
private String contenu;
private String extension;
/**
* Create a file message. These message are used for all interactions regarding file transfer.
*
* The "FICHIER_INIT" messages are used to inform the recipient application that you wish to transfer them files.
* The "FICHIER_ANSWER" messages are answers to "FICHIER_INIT" messages. They indicate that you are ready to receive the file.
* The "contenu" argument then contains the port on which you wish to receive the files.
*
* The "FICHIER" messages contains the files themselves.
* The "IMAGE" messages contains images files, which means the application will display a thumbnail for the image to the recipient.
*
* @param TypeMessage type (must be FICHIER_INIT, FICHIER_ANSWER, FICHIER or IMAGE, else an error is raised)
* @param contenu : message content as String
* @param extension : file extension as string
*
* @throws MauvaisTypeMessageException
*/
public MessageFichier(TypeMessage type, String contenu, String extension) throws MauvaisTypeMessageException{
if ((type==TypeMessage.IMAGE)||(type==TypeMessage.FICHIER) ||(type==TypeMessage.FICHIER_INIT) || (type==TypeMessage.FICHIER_ANSWER) ) {
this.type=type;
this.contenu=contenu;
this.extension=extension;
this.setDateMessage(Message.getDateAndTime());
}
else throw new MauvaisTypeMessageException();
}
// ----- GETTERS ----- //
/**
* Returns content of the message
*
* @return content as String
*/
public String getContenu() {
return this.contenu;
}
/**
* Returns extension of the file contained in the message (if the message contains a file)
*
* @return extension as String
*/
public String getExtension() {
return this.extension;
}
// ----- MESSAGE-STRING CONVERSION METHODS -------//
/**
* Implements attributsToString method of Message
*
* @return attributes as a String
*/
@Override
protected String attributsToString() {
return this.contenu+"###"+this.extension;
}
public String toString() {
if(this.type == TypeMessage.IMAGE) {
return this.contenu;
}else {
String suffixe;
if(this.getSender().equals("Moi")) {
suffixe = "envoyé\n";
}else {
suffixe = "reçu\n";
}
return "<"+this.getDateMessage()+"> : "+this.contenu+" "+suffixe;
}
}
}

View file

@ -0,0 +1,97 @@
package messages;
public class MessageSysteme extends Message {
private static final long serialVersionUID = 1L;
private String pseudo;
private String id;
private int port;
// ------ CONSTRUCTORS ------ //
/**
* Create a system message. These message are used for all system interactions by the UDP channel.
* The "JE_SUIS_CONNECTE" messages are used to inform the network that you just joined.
* They are sent directly after an user log in, and await multiple "INFO_PSEUDO" messages as answers, to build the table of users logged in.
*
* @param TypeMessage type (must be JE_SUIS_CONNECTE, else an error is raised)
* @throws MauvaisTypeMessageException
*/
public MessageSysteme(TypeMessage type) throws MauvaisTypeMessageException{
if (type==TypeMessage.JE_SUIS_CONNECTE) {
this.type=type;
this.pseudo="";
this.id="";
this.port = -1;
}
else throw new MauvaisTypeMessageException();
}
/**
* Create a system message. These message are used for all system interactions by the UDP channel.
* The "JE_SUIS_DECONNECTE" messages are used to inform the network that you just quit it.
*
* The "INFO_PSEUDO" are used to give informations about you to another user, much like an business card.
* They are used either as an answer to a "JE_SUIS_CONNECTE" message or to inform the network of a change of pseudo.
*
* @param TypeMessage type (must be JE_SUIS_DECONNECTE or INFO_PSEUDO, else an error is raised)
* @param pseudo : user pseudo as String
* @param id : user id as String
* @param port : "server" UDP port used by the application (used when the application id in local mode)
*
* @throws MauvaisTypeMessageException
*/
public MessageSysteme(TypeMessage type, String pseudo, String id, int port) throws MauvaisTypeMessageException {
if (type==TypeMessage.INFO_PSEUDO ||(type==TypeMessage.JE_SUIS_DECONNECTE)) {
this.type=type;
this.pseudo=pseudo;
this.id=id;
this.port = port;
}
else throw new MauvaisTypeMessageException();
}
// ----- GETTERS ----- //
/**
* Returns pseudo of the sender of the message (when type == INFO_PSEUDO)
*
* @return user pseudo as String
*/
public String getPseudo() {
return this.pseudo;
}
/**
* Returns id of the sender of the message (when type == INFO_PSEUDO)
*
* @return user id as String
*/
public String getId() {
return this.id;
}
/**
* Returns the "server" UDP port used by the sender of the message
*
* @return port as integer
*/
public int getPort() {
return this.port;
}
// ----- MESSAGE-STRING CONVERSION METHODS -------//
/**
* Implements attributsToString method of Message
*
* @return attributes as a String
*/
@Override
protected String attributsToString() {
return this.pseudo+"###"+this.id+"###"+this.port;
}
}

View file

@ -0,0 +1,55 @@
package messages;
public class MessageTexte extends Message {
private static final long serialVersionUID = 1L;
private String contenu;
/**
* Create a text message. These message are used for basic text conversation via TCP.
*
* @param TypeMessage type (must be TEXT, else an error is raised)
* @param contenu : message content as String
*
* @throws MauvaisTypeMessageException
*/
public MessageTexte(TypeMessage type, String contenu) throws MauvaisTypeMessageException{
if (type==TypeMessage.TEXTE) {
this.type=type;
this.contenu=contenu;
this.setDateMessage(Message.getDateAndTime());
}
else throw new MauvaisTypeMessageException();
}
// ----- GETTERS ----- //
/**
* Returns content of the message
*
* @return content as String
*/
public String getContenu() {
return this.contenu;
}
// ----- MESSAGE-STRING CONVERSION METHODS -------//
/**
* Implements attributsToString method of Message
*
* @return attributes as a String
*/
@Override
protected String attributsToString() {
return this.contenu;
}
@Override
public String toString() {
return "<"+this.getDateMessage()+"> "+this.getSender()+" : "+this.contenu+"\n";
}
}

View file

@ -0,0 +1,12 @@
package observers;
public interface ObserverInputMessage {
/**
* 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

@ -0,0 +1,13 @@
package observers;
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);
}

View file

@ -0,0 +1,17 @@
package observers;
import java.util.ArrayList;
import main.Utilisateur;
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);
}

View file

@ -0,0 +1,352 @@
package session;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
import java.io.IOException;
import java.net.Socket;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import communication.filetransfer.FileTransferClient;
import communication.filetransfer.FileTransferServer;
import communication.tcp.TCPClient;
import database.SQLiteManager;
import main.Utilisateur;
import messages.MauvaisTypeMessageException;
import messages.Message;
import messages.MessageFichier;
import messages.MessageTexte;
import messages.Message.TypeMessage;
import observers.ObserverInputMessage;
import observers.ObserverSocketState;
public class ControleurSession implements ActionListener, ObserverInputMessage, ObserverSocketState, KeyListener {
private VueSession vue;
private String idOther;
private String pseudoOther;
private TCPClient tcpClient;
private ArrayList<Message> messagesIn;
private ArrayList<Message> messagesOut;
private SQLiteManager sqlManager;
private ArrayList<File> files;
/**
* Create the controller for this session. It will manage all the objects used
* to send/receive messages and files as well as the ones interacting with the
* database. It will handle the actions performed on the view and call the
* appropriate methods to display messages and data on the view.
*
* @param vue The corresponding view
* @param socketComm The socket used to send/receive messages
* @param idOther The other user's id
* @param pseudoOther The other user's pseudo
* @param sqlManager The SQLManager instance to retrieve/insert
* users,conversations,messages from/into the database
* @throws IOException
*/
protected ControleurSession(VueSession vue, Socket socketComm, String idOther, String pseudoOther,
SQLiteManager sqlManager) throws IOException {
this.vue = vue;
this.tcpClient = new TCPClient(socketComm);
this.tcpClient.setObserverInputThread(this);
this.tcpClient.setObserverSocketState(this);
this.tcpClient.startInputThread();
this.messagesIn = new ArrayList<Message>();
this.messagesOut = new ArrayList<Message>();
this.idOther = idOther;
this.pseudoOther = pseudoOther;
this.sqlManager = sqlManager;
this.files = new ArrayList<File>();
}
// ---------- ACTION LISTENER OPERATIONS ---------- //
@Override
public void actionPerformed(ActionEvent e) {
// If the button "Envoyer" is pressed
if ((JButton) e.getSource() == this.vue.getButtonEnvoyer()) {
String messageContent = this.vue.getInputedText();
System.out.println(messageContent);
if (!this.files.isEmpty()) {
this.processSelectedFiles(messageContent);
if (!this.files.isEmpty()) {
this.askFileTransfer();
this.vue.resetZoneSaisie();
messageContent = "";
}
}
// If the text field is not empty
if (!messageContent.equals("")) {
// Retrieve the date and prepare the messages to send/display
MessageTexte messageOut = null;
try {
messageOut = new MessageTexte(TypeMessage.TEXTE, messageContent);
messageOut.setSender(Utilisateur.getSelf().getPseudo());
} catch (MauvaisTypeMessageException e2) {
e2.printStackTrace();
}
try {
this.tcpClient.sendMessage(messageOut);
} catch (IOException e1) {
e1.printStackTrace();
}
messageOut.setSender("Moi");
this.vue.appendMessage(messageOut);
this.vue.resetZoneSaisie();
this.messagesOut.add(messageOut);
}
}
// If the button "Importer" is pressed
if ((JButton) e.getSource() == this.vue.getButtonImportFile()) {
// Display a file chooser to select one or several files
JFileChooser fc = new JFileChooser();
fc.setMultiSelectionEnabled(true);
int returVal = fc.showDialog(this.vue, "Importer");
// If the user clicked on "Importer",
// Retrieve all the files he clicked on.
// The files are stored in this.files
// and their names are display in the ChatInput.
if (returVal == JFileChooser.APPROVE_OPTION) {
File[] files = fc.getSelectedFiles();
Collections.addAll(this.files, files);
for (File file : files) {
this.vue.appendInputedText(file.getName());
this.vue.appendInputedText(";");
}
}
}
}
// ---------- KEY LISTENER METHODS ---------- //
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
if (!e.isShiftDown()) {
this.vue.getButtonEnvoyer().doClick();
}
}
}
@Override
public void keyReleased(KeyEvent e) {
}
// ---------- OTHERS ---------- //
/**
* Create and send the message to ask for a file transfer.
*/
private void askFileTransfer() {
try {
MessageFichier messageOut = new MessageFichier(TypeMessage.FICHIER_INIT, "" + this.files.size(), "");
this.tcpClient.sendMessage(messageOut);
} catch (MauvaisTypeMessageException | IOException e1) {
e1.printStackTrace();
}
}
/**
* Create and send the answer with the port on which the FileTransferServer is
* listening.
*
* @param port
*/
private void answerFileTransfer(int port) {
try {
MessageFichier messageOut = new MessageFichier(TypeMessage.FICHIER_ANSWER, "" + port, "");
this.tcpClient.sendMessage(messageOut);
} catch (MauvaisTypeMessageException | IOException e1) {
e1.printStackTrace();
}
}
/**
* Retrieve the files' names from the given input using ";" as a separator
* Removes the files whose names are missing.
*
* This method is used to check if a file's name has been deleted/overwritten in
* the ChatInput. Indeed, the only way to cancel the import of a file is by
* deleting its name from the ChatInput.
*
* @param input
*/
private void processSelectedFiles(String input) {
String[] tmp = input.split(";");
ArrayList<String> potentialFiles = new ArrayList<String>();
Collections.addAll(potentialFiles, tmp);
for (File file : this.files) {
if (!potentialFiles.contains(file.getName())) {
this.files.remove(file);
}
}
}
/**
* Retrieve the messages previously exchanged between the current user of the
* application and the other user of this session
*
* @return The ArrayList of all previous messages
*/
protected ArrayList<Message> getHistorique() {
try {
ArrayList<Message> historique = this.sqlManager.getMessageRecord(idOther, pseudoOther);
return historique;
} catch (SQLException e) {
e.printStackTrace();
return new ArrayList<Message>();
}
}
/**
* Method used when the session is over. Insert every message exchanged in the
* database, set all attributes' references to null, and call destroyAll() on
* the TCPClient.
*/
protected void destroyAll() {
String idSelf = Utilisateur.getSelf().getId();
String idOther = this.idOther;
try {
this.sqlManager.insertAllMessages(messagesOut, idSelf, idOther);
this.sqlManager.insertAllMessages(messagesIn, idOther, idSelf);
} catch (SQLException e) {
e.printStackTrace();
}
this.vue = null;
this.tcpClient.destroyAll();
this.tcpClient = null;
}
// ---------- OBSERVERS ---------- //
// Method called when a message is received from the TCP socket
@Override
public void updateInput(Object o, Object arg) {
Message message = (Message) arg;
switch (message.getTypeMessage()) {
// If it is a simple text message, display it
case TEXTE:
System.out.println(message.toString());
this.vue.appendMessage(message);
this.messagesIn.add(message);
break;
// If it is an image, display a thumbnail
case IMAGE:
this.vue.appendImage(message);
if (message.getSender().equals("Moi")) {
this.messagesOut.add(message);
} else {
this.messagesIn.add(message);
}
break;
// If it is a file, display a message saying whether it has been sent/received.
case FICHIER:
this.vue.appendMessage(message);
if (message.getSender().equals("Moi")) {
this.messagesOut.add(message);
} else {
this.messagesIn.add(message);
}
break;
// If it is a demand for a file transfer, create a new FileTransferServer, start
// it
// and answer back with "FICHIER_ANSWER" message containing the port of the
// server.
case FICHIER_INIT:
try {
MessageFichier mFichier = (MessageFichier) arg;
int nbFile = Integer.parseInt(mFichier.getContenu());
FileTransferServer fts = new FileTransferServer(nbFile, this);
int port = fts.getPort();
fts.start();
this.answerFileTransfer(port);
} catch (IOException e) {
e.printStackTrace();
}
break;
// If it is an answer for a file transfer, create a FilteTransferClient
// with the port received and the path of the file(s) to send,
// and send the files.
case FICHIER_ANSWER:
try {
MessageFichier mFichier = (MessageFichier) arg;
int port = Integer.parseInt(mFichier.getContenu());
@SuppressWarnings("unchecked")
FileTransferClient ftc = new FileTransferClient(port, (ArrayList<File>) this.files.clone(), this);
ftc.sendFiles();
this.files.clear();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
break;
// Do nothing
default:
}
}
// If the other user closes the session or the communication is broken
// Disable the view (TextArea, Buttons..) and display a message
@Override
public void updateSocketState(Object o, Object arg) {
this.vue.endSession(this.pseudoOther);
}
}

View file

@ -0,0 +1,317 @@
package session;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.border.EmptyBorder;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.BoxView;
import javax.swing.text.ComponentView;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.IconView;
import javax.swing.text.LabelView;
import javax.swing.text.ParagraphView;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import javax.swing.text.StyledEditorKit;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import communication.filetransfer.FileTransferUtils;
import database.SQLiteManager;
import messages.Message;
import messages.Message.TypeMessage;
public class VueSession extends JPanel {
private static final long serialVersionUID = 1L;
private JButton sendMessage;
private JButton importFile;
private JTextPane chatWindow;
private JTextArea chatInput;
private ControleurSession c;
public VueSession(Socket socketComm, String idOther, String pseudoOther, SQLiteManager sqlManager)
throws IOException {
this.c = new ControleurSession(this, socketComm, idOther, pseudoOther, sqlManager);
this.setBorder(new EmptyBorder(5, 5, 5, 5));
this.setLayout(new BorderLayout(0, 0));
// Create the display zone
this.chatWindow = new JTextPane();
this.chatWindow.setEditable(false);
this.chatWindow.setEditorKit(new WrapEditorKit());
JScrollPane chatScroll = new JScrollPane(this.chatWindow);
chatScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
// Create the input zone
this.chatInput = new JTextArea();
this.chatInput.setColumns(10);
this.chatInput.setLineWrap(true);
this.chatInput.setWrapStyleWord(true);
this.chatInput.addKeyListener(this.c);
JPanel bottom = new JPanel();
bottom.setLayout(new BorderLayout(0, 0));
// Remap "ENTER" to "none" to avoid "\n" in the input area when pressing "ENTER"
// to send a message
KeyStroke enter = KeyStroke.getKeyStroke("ENTER");
this.chatInput.getInputMap().put(enter, "none");
KeyStroke shiftEnter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.SHIFT_DOWN_MASK);
this.chatInput.getInputMap().put(shiftEnter, "insert-break");
// Create a scroller to be able to send messages of several lines
JScrollPane inputScroll = new JScrollPane(this.chatInput);
inputScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
// Import file button
this.importFile = new JButton("Importer..");
this.importFile.addActionListener(this.c);
// Send message button
this.sendMessage = new JButton("Envoyer");
this.sendMessage.addActionListener(this.c);
bottom.add(this.importFile, BorderLayout.WEST);
bottom.add(inputScroll, BorderLayout.CENTER);
bottom.add(this.sendMessage, BorderLayout.EAST);
// Add the components to the view
this.add(chatScroll, BorderLayout.CENTER);
this.add(bottom, BorderLayout.SOUTH);
this.setPreferredSize(new Dimension(500, 500));
this.displayHistorique();
}
// -------------- GETTERS -------------- //
protected JButton getButtonEnvoyer() {
return this.sendMessage;
}
protected JButton getButtonImportFile() {
return this.importFile;
}
protected String getInputedText() {
return this.chatInput.getText();
}
// -------------- DISPLAY METHODS -------------- //
/**
* Append the given string to the ChatWindow.
*
* @param str
*/
protected void appendString(String str) {
try {
Document doc = this.chatWindow.getDocument();
doc.insertString(doc.getLength(), str, null);
} catch (BadLocationException e) {
e.printStackTrace();
}
}
/**
* Append the given Message to the ChatWindow.
*
* @param message
*/
protected void appendMessage(Message message) {
try {
StyledDocument sdoc = this.chatWindow.getStyledDocument();
sdoc.insertString(sdoc.getLength(), message.toString(), null);
} catch (BadLocationException e) {
e.printStackTrace();
}
}
/**
* Append an icon with the image contained in the given message. The message has
* to contain the base64 encoded bytes of the image as a String for it to be
* decoded and displayed.
*
* @param message
*/
protected void appendImage(Message message) {
this.setCaretToEnd();
String imgString = message.toString();
Icon ic;
try {
BufferedImage img = FileTransferUtils.decodeImage(imgString);
ic = new ImageIcon(img);
this.chatWindow.insertIcon(ic);
this.appendString("\n");
} catch (IOException e) {
e.printStackTrace();
}
}
private void printLineSeparator() {
this.appendString("------------------------------------------\n");
}
/**
* Append the given string to the ChatInput.
*
* @param str
*/
protected void appendInputedText(String str) {
this.chatInput.append(str);
}
protected void resetZoneSaisie() {
this.chatInput.setText("");
}
// -------------- OTHERS -------------- //
private void setCaretToEnd() {
this.chatWindow.setCaretPosition(this.chatWindow.getDocument().getLength());
}
/**
* Retrieve all the previous messages from the controller and display them by
* appending them one by one to the ChatWindow.
*/
private void displayHistorique() {
ArrayList<Message> historique = this.c.getHistorique();
for (Message m : historique) {
if (m.getTypeMessage() == TypeMessage.IMAGE) {
this.appendImage(m);
} else {
this.appendMessage(m);
}
}
if (historique.size() > 0) {
this.printLineSeparator();
}
}
/**
* Disable the ChatInput, the buttons "Importer" and "Envoyer" and display a
* message indicating the other user ended the session.
*
* @param pseudoOther
*/
protected void endSession(String pseudoOther) {
this.printLineSeparator();
this.appendString(pseudoOther + " a mis fin à la session.");
this.chatInput.setEnabled(false);
this.chatInput.setFocusable(false);
this.sendMessage.setEnabled(false);
this.importFile.setEnabled(false);
}
/**
* Method used when the user closes the session. Set all attributes' references
* to null, and call destroyAll() on the controller.
*/
public void destroyAll() {
if (this.c != null) {
this.c.destroyAll();
}
this.c = null;
this.chatInput = null;
this.chatWindow = null;
this.sendMessage = null;
}
// ------------- PRIVATE CLASS TO WRAP TEXT -------------//
class WrapEditorKit extends StyledEditorKit {
private static final long serialVersionUID = 1L;
ViewFactory defaultFactory = new WrapColumnFactory();
public ViewFactory getViewFactory() {
return defaultFactory;
}
}
class WrapColumnFactory implements ViewFactory {
public View create(Element elem) {
String kind = elem.getName();
if (kind != null) {
if (kind.equals(AbstractDocument.ContentElementName)) {
return new WrapLabelView(elem);
} else if (kind.equals(AbstractDocument.ParagraphElementName)) {
return new ParagraphView(elem);
} else if (kind.equals(AbstractDocument.SectionElementName)) {
return new BoxView(elem, View.Y_AXIS);
} else if (kind.equals(StyleConstants.ComponentElementName)) {
return new ComponentView(elem);
} else if (kind.equals(StyleConstants.IconElementName)) {
return new IconView(elem);
}
}
// default to text display
return new LabelView(elem);
}
}
class WrapLabelView extends LabelView {
public WrapLabelView(Element elem) {
super(elem);
}
public float getMinimumSpan(int axis) {
switch (axis) {
case View.X_AXIS:
return 0;
case View.Y_AXIS:
return super.getMinimumSpan(axis);
default:
throw new IllegalArgumentException("Invalid axis: " + axis);
}
}
}
}

View file

@ -0,0 +1,326 @@
package standard;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JList;
import javax.swing.WindowConstants;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import communication.tcp.TCPServer;
import communication.udp.CommunicationUDP;
import connexion.VueConnexion;
import database.SQLiteManager;
import main.Utilisateur;
import observers.ObserverInputMessage;
import observers.ObserverSocketState;
import observers.ObserverUserList;
import session.VueSession;
public class ControleurStandard implements ActionListener, ListSelectionListener, WindowListener, ObserverInputMessage,
ObserverUserList, ObserverSocketState {
private enum ModifPseudo {
TERMINE, EN_COURS
}
private ModifPseudo modifPseudo;
private VueStandard vue;
private CommunicationUDP commUDP;
private String lastPseudo;
private TCPServer tcpServ;
private ArrayList<String> idsSessionEnCours;
private SQLiteManager sqlManager;
private VueConnexion vueConnexion;
public ControleurStandard(VueStandard vue, CommunicationUDP commUDP, int portServerTCP, SQLiteManager sqlManager,
VueConnexion vueConnexion) throws IOException {
this.vue = vue;
this.vue.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
this.vueConnexion = vueConnexion;
this.tcpServ = new TCPServer(portServerTCP);
this.tcpServ.addObserver(this);
this.tcpServ.start();
this.commUDP = commUDP;
this.idsSessionEnCours = new ArrayList<String>();
this.sqlManager = sqlManager;
}
// ---------- LISTSELECTION LISTENER OPERATIONS ----------//
@Override
public void valueChanged(ListSelectionEvent e) {
// Case when a list element is selected
if (this.vue.getActiveUsersList().isFocusOwner() && !e.getValueIsAdjusting()
&& this.vue.getActiveUsersList().getSelectedValue() != null) {
JList<String> list = this.vue.getActiveUsersList();
String pseudoOther = list.getSelectedValue();
Utilisateur other = this.commUDP.getUserFromPseudo(pseudoOther);
String idOther = other.getId();
// Check if we are already asking for a session/chatting with the person
// selected
// null condition because the list.clearSelection() generates an event
if (!this.idsSessionEnCours.contains(idOther)) {
int choix = this.vue.displayJOptionSessionCreation(pseudoOther);
System.out.println("choix : " + choix);
if (choix == 0) {
int port = other.getPort();
System.out.println("port = " + port);
try {
Socket socketComm = new Socket(InetAddress.getLocalHost(), port);
this.sendMessage(socketComm, Utilisateur.getSelf().getPseudo());
String reponse = this.readMessage(socketComm);
System.out.println("reponse : " + reponse);
if (reponse.equals("accepted")) {
this.idsSessionEnCours.add(idOther);
VueSession session = new VueSession(socketComm, idOther, pseudoOther, this.sqlManager);
this.vue.addSession(pseudoOther, session);
this.vue.displayJOptionResponse("acceptee");
} else {
this.vue.displayJOptionResponse("refusee");
socketComm.close();
System.out.println("refused");
}
} catch (IOException e1) {
vue.displayJOptionResponse("refusee");
}
}
}
list.clearSelection();
System.out.println("pseudo de la personne a atteindre : " + pseudoOther);
}
}
// ---------- ACTION LISTENER OPERATIONS ----------//
@Override
public void actionPerformed(ActionEvent e) {
// Cas Modifier Pseudo
if ((JButton) e.getSource() == this.vue.getButtonModifierPseudo()) {
JButton modifierPseudo = (JButton) e.getSource();
if (this.modifPseudo == ModifPseudo.TERMINE) {
this.lastPseudo = Utilisateur.getSelf().getPseudo();
modifierPseudo.setText("OK");
this.modifPseudo = ModifPseudo.EN_COURS;
} else {
if (this.vue.getDisplayedPseudo().length() >= 1
&& !this.commUDP.containsUserFromPseudo(this.vue.getDisplayedPseudo().toLowerCase())) {
Utilisateur.getSelf().setPseudo(this.vue.getDisplayedPseudo());
try {
this.commUDP.sendMessageInfoPseudo();
} catch (IOException e1) {
e1.printStackTrace();
}
} else {
this.vue.setDisplayedPseudo(this.lastPseudo);
}
modifierPseudo.setText("Modifier");
this.modifPseudo = ModifPseudo.TERMINE;
}
this.vue.toggleEditPseudo();
}
// Cas deconnexion
else if ((JButton) e.getSource() == this.vue.getButtonDeconnexion()) {
try {
this.setVueConnexion();
} catch (IOException e1) {
e1.printStackTrace();
}
}
else if (this.vue.isButtonTab(e.getSource())) {
JButton button = (JButton) e.getSource();
int index = this.vue.removeSession(button);
this.idsSessionEnCours.remove(index);
}
}
// ---------- WINDOW LISTENER OPERATIONS ----------//
@Override
public void windowClosing(WindowEvent e) {
try {
this.setVueConnexion();
} catch (IOException e1) {
e1.printStackTrace();
}
}
@Override
public void windowOpened(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void windowClosed(WindowEvent e) {
}
@Override
public void windowIconified(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void windowDeiconified(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void windowActivated(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void windowDeactivated(WindowEvent e) {
// TODO Auto-generated method stub
}
// ------------SOCKET-------------//
private void sendMessage(Socket sock, String message) throws IOException {
PrintWriter output = new PrintWriter(sock.getOutputStream(), true);
output.println(message);
}
private String readMessage(Socket sock) throws IOException {
BufferedReader input = new BufferedReader(new InputStreamReader(sock.getInputStream()));
return input.readLine();
}
// ------------OBSERVERS------------- //
@Override
public void updateInput(Object o, Object arg) {
if (o == this.tcpServ) {
Socket sockAccept = (Socket) arg;
try {
String pseudoOther = this.readMessage(sockAccept);
String idOther = this.commUDP.getUserFromPseudo(pseudoOther).getId();
int reponse;
if (!this.idsSessionEnCours.contains(idOther)) {
reponse = this.vue.displayJOptionAskForSession(pseudoOther);
System.out.println("reponse : " + reponse);
} else {
reponse = 1;
}
if (reponse == 0) {
this.idsSessionEnCours.add(idOther);
this.sendMessage(sockAccept, "accepted");
VueSession session = new VueSession(sockAccept, idOther, pseudoOther, this.sqlManager);
this.vue.addSession(pseudoOther, session);
} else {
this.sendMessage(sockAccept, "refused");
sockAccept.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void updateList(Object o, ArrayList<Utilisateur> userList) {
if (o == this.commUDP) {
ArrayList<String> pseudos = new ArrayList<String>();
for (Utilisateur u : userList) {
pseudos.add(u.getPseudo());
}
this.vue.setActiveUsersList(pseudos);
}
}
@Override
public void updateSocketState(Object o, Object arg) {
VueSession session = (VueSession) arg;
int index = this.vue.removeSession(session);
this.idsSessionEnCours.remove(index);
}
private void setVueConnexion() throws UnknownHostException, IOException {
this.commUDP.sendMessageDelete();
this.commUDP.removeAllUsers();
this.vue.removeAllUsers();
this.vue.closeAllSession();
this.idsSessionEnCours.clear();
Utilisateur.resetSelf();
this.commUDP.setObserver(null);
this.vue.setVisible(false);
this.vueConnexion.setVisible(true);
}
protected void init() throws UnknownHostException, IOException {
this.commUDP.setObserver(this);
this.commUDP.sendMessageConnecte();
this.commUDP.sendMessageInfoPseudo();
this.modifPseudo = ModifPseudo.TERMINE;
}
}

View file

@ -0,0 +1,373 @@
package standard;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Iterator;
import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.plaf.basic.BasicButtonUI;
import communication.udp.CommunicationUDP;
import connexion.VueConnexion;
import database.SQLiteManager;
import main.Utilisateur;
import main.Vue;
import session.VueSession;
public class VueStandard extends Vue {
/**
*
*/
private static final long serialVersionUID = 1L;
private JList<String> activeUsersList;
private JTextField pseudoSelf;
private JTabbedPane zoneSessions;
private JButton modifierPseudo;
private JButton seConnecter;
private JButton seDeconnecter;
private ControleurStandard c;
private ArrayList<JButton> tabButtons;
private ArrayList<VueSession> sessions;
private DefaultListModel<String> userList = new DefaultListModel<String>();
public VueStandard(String title, CommunicationUDP commUDP, int portServerTCP, SQLiteManager sqlManager, VueConnexion vueConnexion) throws IOException {
super(title);
this.tabButtons = new ArrayList<JButton>();
this.sessions = new ArrayList<VueSession>();
this.c = new ControleurStandard(this, commUDP, portServerTCP, sqlManager, vueConnexion);
this.c.init();
getContentPane().setLayout(new GridBagLayout());
JPanel left = new JPanel(new BorderLayout());
this.zoneSessions = new JTabbedPane();
this.zoneSessions.setTabPlacement(JTabbedPane.BOTTOM);
this.zoneSessions.setPreferredSize(new Dimension(600, 600));
// --------Panel haut pseudo--------//
JPanel self = new JPanel(new FlowLayout());
this.pseudoSelf = new JTextField(Utilisateur.getSelf().getPseudo());
this.pseudoSelf.setPreferredSize(new Dimension(100, 30));
this.pseudoSelf.setEditable(false);
this.pseudoSelf.setFocusable(false);
this.pseudoSelf.setEnabled(false);
this.modifierPseudo = new JButton("Modifier");
this.modifierPseudo.addActionListener(this.c);
self.add(new JLabel("Moi : "));
self.add(this.pseudoSelf);
self.add(this.modifierPseudo);
this.pseudoSelf.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
modifierPseudo.doClick();
}
}
});
// --------Panel milieu liste utilisateurs--------//
this.activeUsersList = new JList<String>(this.userList);
this.activeUsersList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
this.activeUsersList.setLayoutOrientation(JList.VERTICAL);
this.activeUsersList.addListSelectionListener(this.c);
System.out.println("listener ajouté");
JScrollPane listScroller = new JScrollPane(this.activeUsersList);
listScroller.setPreferredSize(new Dimension(50, 50));
listScroller.setAlignmentX(LEFT_ALIGNMENT);
listScroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
listScroller.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createTitledBorder("Utilisateurs Actifs"), BorderFactory.createEmptyBorder(5, 2, 2, 2)));
// --------Panel bas deconnexion--------//
JPanel deconnexion = new JPanel(new GridLayout(1, 2));
this.seDeconnecter = new JButton("Se Déconnecter");
this.seDeconnecter.addActionListener(this.c);
deconnexion.add(this.seDeconnecter);
if(Utilisateur.getSelf().getId() == "admin") {
JButton addNewUser = new JButton("Ajouter un nouvel utilisateur");
deconnexion.add(addNewUser);
}
// --------Ajout à la vue--------//
left.add(self, BorderLayout.PAGE_START);
left.add(listScroller, BorderLayout.CENTER);
left.add(deconnexion, BorderLayout.PAGE_END);
GridBagConstraints gridBagConstraintLeft = new GridBagConstraints();
GridBagConstraints gridBagConstraintSessions = new GridBagConstraints();
gridBagConstraintLeft.fill = GridBagConstraints.BOTH;
gridBagConstraintLeft.gridx = 0;
gridBagConstraintLeft.gridy = 0;
gridBagConstraintLeft.gridwidth = 1;
gridBagConstraintLeft.gridheight = 4;
gridBagConstraintLeft.weightx = 0.33;
gridBagConstraintLeft.weighty = 1;
getContentPane().add(left, gridBagConstraintLeft);
gridBagConstraintSessions.fill = GridBagConstraints.BOTH;
gridBagConstraintSessions.gridx = 1;
gridBagConstraintSessions.gridy = 0;
gridBagConstraintSessions.gridwidth = 2;
gridBagConstraintSessions.gridheight = 4;
gridBagConstraintSessions.weightx = 0.66;
gridBagConstraintSessions.weighty = 1;
getContentPane().add(this.zoneSessions, gridBagConstraintSessions);
this.pack();
this.setVisible(true);
this.addWindowListener(c);
}
// ------------ GETTERS -------------//
protected JList<String> getActiveUsersList() {
return this.activeUsersList;
}
protected JButton getButtonModifierPseudo() {
return this.modifierPseudo;
}
protected JButton getButtonDeconnexion() {
return this.seDeconnecter;
}
protected JButton getButtonConnexion() {
return this.seConnecter;
}
protected String getDisplayedPseudo() {
return this.pseudoSelf.getText();
}
// ------------ SETTERS -------------//
protected void setActiveUsersList(ArrayList<String> users) {
this.removeAllUsers();
this.userList.addAll(users);
}
protected void setDisplayedPseudo(String pseudo) {
this.pseudoSelf.setText(pseudo);
}
public void setPseudoSelf() {
this.setDisplayedPseudo(Utilisateur.getSelf().getPseudo());
}
// ------------ JOPTIONS -------------//
protected int displayJOptionSessionCreation(String pseudo) {
return JOptionPane.showConfirmDialog(this, "Voulez vous créer une session avec " + pseudo + " ?",
"Confirmation session", JOptionPane.YES_NO_OPTION);
}
protected int displayJOptionAskForSession(String pseudo) {
return JOptionPane.showConfirmDialog(this, pseudo + " souhaite creer une session avec vous.",
"Accepter demande", JOptionPane.YES_NO_OPTION);
}
protected void displayJOptionResponse(String reponse) {
JOptionPane.showMessageDialog(this, "Demande de session " + reponse);
}
// ------------ TOGGLEBUTTONS -------------//
protected void toggleEditPseudo() {
this.pseudoSelf.setEditable(!this.pseudoSelf.isEditable());
this.pseudoSelf.setFocusable(!this.pseudoSelf.isFocusable());
this.pseudoSelf.setEnabled(!this.pseudoSelf.isEnabled());
}
protected void toggleEnableButtonDeconnexion() {
this.seDeconnecter.setEnabled(!this.seDeconnecter.isEnabled());
}
protected void toggleEnableButtonConnexion() {
this.seConnecter.setEnabled(!this.seConnecter.isEnabled());
}
// ------------SESSION-------------//
protected boolean isButtonTab(Object o) {
return this.tabButtons.contains(o);
}
protected int removeSession(JButton button) {
int index = this.tabButtons.indexOf(button);
VueSession vue = this.sessions.get(index);
vue.destroyAll();
this.zoneSessions.remove(vue);
this.sessions.remove(index);
this.tabButtons.remove(index);
return index;
}
protected void addSession(String pseudo, VueSession session) {
JPanel tabTitle = new JPanel();
TabButton closeTab = new TabButton();
tabTitle.add(new JLabel(pseudo));
tabTitle.add(closeTab);
this.zoneSessions.addTab(pseudo, session);
this.zoneSessions.setTabComponentAt(this.zoneSessions.getTabCount() - 1, tabTitle);
this.tabButtons.add(closeTab);
this.sessions.add(session);
session.requestFocus();
}
protected synchronized int removeSession(VueSession vue) {
int index = this.sessions.indexOf(vue);
vue.destroyAll();
this.zoneSessions.remove(vue);
this.sessions.remove(index);
this.tabButtons.remove(index);
return index;
}
protected void closeAllSession() {
Iterator<VueSession> it = this.sessions.iterator();
while(it.hasNext()) {
VueSession session = it.next();
this.zoneSessions.remove(session);
session.destroyAll();
it.remove();
}
this.tabButtons.clear();
}
// ------------ OTHERS -------------//
protected void removeAllUsers() {
this.userList.removeAllElements();
}
public void initControleur() throws UnknownHostException, IOException {
this.c.init();
}
// ------------- PRIVATE CLASSES FOR THE TABS BUTTON -------------//
private class TabButton extends JButton {
private static final long serialVersionUID = 1L;
public TabButton() {
int size = 17;
setPreferredSize(new Dimension(size, size));
setToolTipText("close this tab");
// Make the button looks the same for all Laf's
setUI(new BasicButtonUI());
// Make it transparent
setContentAreaFilled(false);
// No need to be focusable
setFocusable(false);
setBorder(BorderFactory.createEtchedBorder());
setBorderPainted(false);
addMouseListener(VueStandard.buttonMouseListener);
// Making nice rollover effect
// we use the same listener for all buttons
addActionListener(c);
setRolloverEnabled(true);
}
// we don't want to update UI for this button
public void updateUI() {
}
// paint the cross
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
// shift the image for pressed buttons
if (getModel().isPressed()) {
g2.translate(1, 1);
}
g2.setStroke(new BasicStroke(2));
g2.setColor(Color.BLACK);
if (getModel().isRollover()) {
g2.setColor(Color.MAGENTA);
}
int delta = 6;
g2.drawLine(delta, delta, getWidth() - delta - 1, getHeight() - delta - 1);
g2.drawLine(getWidth() - delta - 1, delta, delta, getHeight() - delta - 1);
g2.dispose();
}
}
private final static MouseListener buttonMouseListener = new MouseAdapter() {
public void mouseEntered(MouseEvent e) {
Component component = e.getComponent();
if (component instanceof AbstractButton) {
AbstractButton button = (AbstractButton) component;
button.setBorderPainted(true);
}
}
public void mouseExited(MouseEvent e) {
Component component = e.getComponent();
if (component instanceof AbstractButton) {
AbstractButton button = (AbstractButton) component;
button.setBorderPainted(false);
}
}
};
}

View file

@ -0,0 +1,218 @@
package communication;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import main.Observer;
import main.Utilisateur;
import messages.*;
public class CommunicationUDP extends Thread {
private UDPClient client;
private int portServer;
private ArrayList<Integer> portOthers;
private ArrayList<Utilisateur> users = new ArrayList<Utilisateur>();
private Observer observer;
public CommunicationUDP(int portClient, int portServer, int[] portsOther) throws IOException {
this.portServer = portServer;
this.portOthers = this.getArrayListFromArray(portsOther);
new UDPServer(portServer, this).start();
this.client = new UDPClient(portClient);
}
private ArrayList<Integer> getArrayListFromArray(int ports[]) {
ArrayList<Integer> tmp = new ArrayList<Integer>();
for (int port : ports) {
tmp.add(port);
}
tmp.remove(Integer.valueOf(portServer));
return tmp;
}
// ----- CHECKERS ----- //
protected boolean containsUserFromID(String id) {
for(Utilisateur u : users) {
if(u.getId().equals(id) ) {
return true;
}
}
return false;
}
public boolean containsUserFromPseudo(String pseudo) {
for(Utilisateur u : users) {
if(u.getPseudo().equals(pseudo) ) {
return true;
}
}
return false;
}
// ----- GETTERS ----- //
public int getPortFromPseudo(String pseudo) {
for(int i=0; i < users.size() ; i++) {
if(users.get(i).getPseudo().equals(pseudo) ) {
return users.get(i).getPort();
}
}
return -1;
}
private int getIndexFromID(String id) {
for(int i=0; i < users.size() ; i++) {
if(users.get(i).getId().equals(id) ) {
return i;
}
}
return -1;
}
public Utilisateur getUserFromID(String id) {
for(Utilisateur u : users) {
if(u.getId().equals(id) ) {
return u;
}
}
return null;
}
public Observer getObserver () {
return this.observer;
}
// ----- SETTERS ----- //
public void setObserver (Observer obs) {
this.observer=obs;
}
// ----- USER LIST MANAGEMENT ----- //
//Prints a html table containing the pseudo of all active local users
public void printActiveUsersUDP(PrintWriter out) {
for (Utilisateur uIn : users) {
out.println("<TH> " + uIn.getPseudo() + ",</TH>");
}
}
//Add an user to the list of active local users
protected synchronized void addUser(String idClient, String pseudoClient, InetAddress ipClient, int port) throws IOException {
users.add(new Utilisateur(idClient, pseudoClient, ipClient, port));
try {
Message message = new MessageSysteme(Message.TypeMessage.INFO_PSEUDO, idClient, pseudoClient, port);
observer.update(this, message);
} catch (MauvaisTypeMessageException e) {
}
}
//Change the pseudo of an user already in the active local users list
protected synchronized void changePseudoUser(String idClient, String pseudoClient, InetAddress ipClient, int port) {
int index = getIndexFromID(idClient);
users.get(index).setPseudo(pseudoClient);
try {
Message message = new MessageSysteme(Message.TypeMessage.INFO_PSEUDO, idClient, pseudoClient, port);
observer.update(this, message);
} catch (MauvaisTypeMessageException e) {
}
}
//Remove an user from the active local users list
protected synchronized void removeUser(String idClient, String pseudoClient,InetAddress ipClient, int port) {
int index = getIndexFromID(idClient);
if( index != -1) {
users.remove(index);
}
try {
Message message = new MessageSysteme(Message.TypeMessage.JE_SUIS_DECONNECTE, pseudoClient, idClient, port);
observer.update(this, message);
} catch (MauvaisTypeMessageException e) {
}
}
//Remove all users from the active local users list
public void removeAll(){
int oSize = users.size();
for(int i=0; i<oSize;i++) {
users.remove(0);
}
}
// ----- SENDING MESSAGES ----- //
// Send the message "add,id,pseudo" to localhost on all the ports in
// "portOthers"
// This allows the receivers' agent (portOthers) to create or modify an entry with the
// data of this agent
//Typically used to notify of a name change
public void sendMessageInfoPseudo() throws UnknownHostException, IOException {
Utilisateur self = Utilisateur.getSelf();
String pseudoSelf =self.getPseudo();
String idSelf = self.getId();
int portSelf = self.getPort();
Message msout = null;
try {
msout = new MessageSysteme(Message.TypeMessage.INFO_PSEUDO, pseudoSelf, idSelf, portSelf);
for(int port : this.portOthers) {
this.client.sendMessageUDP_local(msout, port, InetAddress.getLocalHost());
}
} catch (Exception e) {
}
}
//Same, but on only one port
//Typically used to give your current name and id to a newly arrived host
public void sendMessageInfoPseudo(int portOther) throws UnknownHostException, IOException {
Utilisateur self = Utilisateur.getSelf();
try {
Message msout = new MessageSysteme(Message.TypeMessage.INFO_PSEUDO, self.getPseudo(), self.getId(), self.getPort());
this.client.sendMessageUDP_local(msout, portOther, InetAddress.getLocalHost());
} catch (MauvaisTypeMessageException e) {e.printStackTrace();}
}
//Broadcast a given message on the local network (here modelized by ports)
public void sendMessage(Message m) {
try {
for(int port : this.portOthers) {
this.client.sendMessageUDP_local(m, port, InetAddress.getLocalHost());
}
} catch (IOException e) {
}
}
//Send a given message to a specific user (here, by port)
public void sendMessage(Message m, int port) {
try {
this.client.sendMessageUDP_local(m, port, InetAddress.getLocalHost());
} catch (IOException e) {
}
}
}

View file

@ -0,0 +1,62 @@
package communication;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
//import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import messages.*;
public class UDPClient {
private DatagramSocket sockUDP;
//private InetAddress broadcast;
/**
* Create an 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 {
this.sockUDP = new DatagramSocket(port);
}
/**
* Send a message to the specified port on localhost.
*
* @param message
* @param port
* @throws IOException
*/
protected void sendMessageUDP_local(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);
}
/**
* 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);
}
}

View file

@ -0,0 +1,86 @@
package communication;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import main.Utilisateur;
import messages.*;
public class UDPServer extends Thread {
private DatagramSocket sockUDP;
private CommunicationUDP commUDP;
private byte[] buffer;
/**
* Create an 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 {
this.commUDP = commUDP;
this.sockUDP = new DatagramSocket(port);
this.buffer = new byte[256];
}
@Override
public void run() {
while (true) {
try {
DatagramPacket inPacket = new DatagramPacket(buffer, buffer.length);
this.sockUDP.receive(inPacket);
String msgString = new String(inPacket.getData(), 0, inPacket.getLength());
Message msg = Message.stringToMessage(msgString);
switch(msg.getTypeMessage()) {
case JE_SUIS_CONNECTE :
if(Utilisateur.getSelf() != null) {
//System.out.println("first co");
int portClient = inPacket.getPort();
int portServer = portClient+1;
this.commUDP.sendMessageInfoPseudo(portServer);
this.commUDP.getObserver().update(this, portServer);
}
break;
case INFO_PSEUDO :
if (this.commUDP.containsUserFromID(((MessageSysteme) msg).getId())) {
this.commUDP.changePseudoUser(((MessageSysteme) msg).getId(), ((MessageSysteme) msg).getPseudo(), inPacket.getAddress(),((MessageSysteme) msg).getPort());
}
else {
this.commUDP.addUser(((MessageSysteme) msg).getId(), ((MessageSysteme) msg).getPseudo(), inPacket.getAddress(), ((MessageSysteme) msg).getPort() );
System.out.println(((MessageSysteme) msg).getId()+", "+((MessageSysteme) msg).getPseudo());
}
break;
case JE_SUIS_DECONNECTE :
this.commUDP.removeUser( ((MessageSysteme) msg).getId() , ((MessageSysteme) msg).getPseudo(), inPacket.getAddress(), inPacket.getPort());
break;
default : //Others types of messages are ignored because they are supposed to be transmitted by TCP and not UDP
}
} catch (IOException e) {
System.out.println("receive exception");
}
}
}
}

View file

@ -0,0 +1,6 @@
package main;
public interface Observer {
public void update(Object o, Object arg);
}

View file

@ -0,0 +1,323 @@
package main;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import communication.CommunicationUDP;
import messages.*;
/**
* Servlet implementation class ServletPresence
*/
@WebServlet("/ServletPresence")
public class ServletPresence extends HttpServlet implements Observer {
private static final long serialVersionUID = 1L;
private CommunicationUDP comUDP;
private ArrayList<Utilisateur> remoteUsers;
private String[] registeredRemoteUsers = {"user1","user2","user3"};
public ServletPresence() {
try {
comUDP = new CommunicationUDP(3333, 3334, new int[] {2209, 2309, 2409, 2509, 3334});
} catch (IOException e) {
}
comUDP.setObserver(this);
remoteUsers = new ArrayList<Utilisateur>();
try {
Utilisateur.setSelf("serv_p", "Serveur de presence", "localhost", 3334);
} catch (UnknownHostException e) {
}
}
// ----- REMOTE USER LIST MANAGEMENT ----- //
private int getIndexByID(String id) {
for(int i=0; i < remoteUsers.size() ; i++) {
if(remoteUsers.get(i).getId().equals(id) ) {
return i;
}
}
return -1;
}
private boolean remoteContainsUserFromPseudo(String pseudo) {
for(Utilisateur u : remoteUsers) {
if(u.getPseudo().equals(pseudo) ) {
return true;
}
}
return false;
}
// ----- HTML PRINT METHODS ----- //
//Affiche la liste des utilisateurs actifs
private void printActiveUsers(PrintWriter out) {
out.println( "<TABLE>" );
out.println("<TH>Utilisateurs connectés : </TH>");
for (Utilisateur uExt : remoteUsers) {
out.println("<TH> " + uExt.getPseudo() + ",</TH>");
}
comUDP.printActiveUsersUDP(out);
out.println( "</TABLE>" );
}
//Affiche la liste des utilisateurs actifs, seule sur la page
private void printActiveUsersOnly(PrintWriter out) {
out.println( "<HTML>" );
out.println( "<HEAD>");
out.println( "<TITLE>Serveur de présence</TITLE>" );
out.println( "</HEAD>" );
out.println( "<BODY>" );
printActiveUsers(out);
out.println( "</BODY>" );
out.println( "</HTML>" );
}
//Affiche la page d'accueil
private void printHomePage(PrintWriter out) {
out.println( "<HTML>" );
out.println( "<HEAD>");
out.println( "<TITLE>Serveur de présence - Accueil</TITLE>" );
out.println( "</HEAD>" );
out.println( "<BODY>" );
out.println( "<H1>Bienvenue sur le service de connexion à distance</H1>" );
out.println( "<H2>Vous pouvez taper votre requête dans la barre de navigation</H2>" );
out.println( "<H2>Requêtes (à ajouter derrière l'URL) : </H2>" );
out.println( "<H3>Se connecter : ?type=POST&id=[votre id]&pseudo=[pseudo voulu]&port=[port utilisé] </H3>" );
out.println( "<H3>Se déconnecter : ?type=DELETE&id=[votre id] </H3>" );
out.println( "<H3>Changer de pseudo : ?type=PUT&id=[votre id]&pseudo=[pseudo voulu] </H3>" );
out.println( "<H3>Rafraîchir la liste des utilisateurs : ?type=GET" );
out.println( "</BODY>" );
out.println( "</HTML>" );
}
//Affiche un message d'erreur en cas de pseudo déjà utilisé
private void printErrorUsedPseudo(PrintWriter out){
out.println( "<HTML>" );
out.println( "<HEAD>");
out.println( "<TITLE>Erreur pseudo déjà utilisé</TITLE>" );
out.println( "</HEAD>" );
out.println( "<BODY>" );
out.println( "<H1>Erreur : Ce pseudo est déjà utilisé</H1>" );
out.println( "<H2>Veuillez choisir un autre pseudo</H2>" );
out.println( "</BODY>" );
printActiveUsers(out);
out.println( "</HTML>" );
}
//Affiche un message d'erreur en cas de requête invalide
private void printErrorInvalidRequest(PrintWriter out) {
out.println( "<HTML>" );
out.println( "<HEAD>");
out.println( "<TITLE>Erreur requête invalide</TITLE>" );
out.println( "</HEAD>" );
out.println( "<BODY>" );
out.println( "<H1>Erreur : nous n'avons pas compris votre requête</H1>" );
out.println( "<H2>Veuillez vérifier votre syntaxe et réessayer</H2>" );
out.println( "</BODY>" );
out.println( "</HTML>" );
}
//Affiche un message d'erreur en cas d'id inconnue
private void printErrorUnkwownUser(PrintWriter out) {
out.println( "<HTML>" );
out.println( "<HEAD>");
out.println( "<TITLE>Erreur Utilisateur Inconnu</TITLE>" );
out.println( "</HEAD>" );
out.println( "<BODY>" );
out.println( "<H1>Erreur : l'id que vous avez entrée n'est pas enregistrée</H1>" );
out.println( "<H2>Veuillez vérifier votre syntaxe et réessayer</H2>" );
out.println( "</BODY>" );
out.println( "</HTML>" );
}
// ----- NOTIFY METHOD ----- //
//Informe de la modification de la liste tous les utilisateurs internes et externes : appelée automatiquement à chaque modification de la liste
private void snotify(MessageSysteme message, Utilisateur user) {
if (remoteUsers.contains(user)) {
//diffuse le message localement, envoie la nouvelle liste des utilisateurs aux utilisateurs externes SAUF L'EXPEDITEUR
comUDP.sendMessage(message);
for (Utilisateur u : remoteUsers) {
if (!u.equals(user)) {
comUDP.sendMessage(message, u.getPort());
}
}
}
else {
//envoie la nouvelle liste des utilisateurs aux utilisateurs externes
for (Utilisateur u : remoteUsers) {
comUDP.sendMessage(message, u.getPort());
}
}
}
// ----- HTTP METHODS ----- //
// susbribe/unsubscribe : Permet a un utilisateur externe de s'ajouter/s'enlever à la liste des utilisateurs externes : au tout début de l'application
//Note : le serveur agit comme un proxy pour le TCP et remplace le port de l'utilisateur par le sien
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String id = request.getParameter("id");
String pseudo = request.getParameter("pseudo");
int port = Integer.parseInt(request.getParameter("port"));
InetAddress ip = InetAddress.getByName(request.getRemoteAddr());
response.setContentType( "text/html" );
PrintWriter out = response.getWriter();
//Si l'id n'existe pas dans la BDD : génère du html pour en informer l'utilisateur
try {
if (!Arrays.asList(registeredRemoteUsers).contains(id)) {
printErrorUnkwownUser(out);
}
//Si le pseudo est déjà pris : idem
else if (comUDP.containsUserFromPseudo(pseudo)||remoteContainsUserFromPseudo(pseudo)) {
printErrorUsedPseudo(out);
}
//Sinon
else {
Utilisateur user = new Utilisateur(id, pseudo, ip, port);
remoteUsers.add(user);
try {
snotify(new MessageSysteme(Message.TypeMessage.INFO_PSEUDO, pseudo, id, 3334), user);
} catch (MauvaisTypeMessageException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
printActiveUsersOnly(out);
}
} catch (UnknownHostException e) {
}
out.close();
}
protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
String id = request.getParameter("id");
int index = getIndexByID(id);
Utilisateur user = remoteUsers.get(index);
try {
snotify(new MessageSysteme(Message.TypeMessage.JE_SUIS_DECONNECTE,"", id, 3334), user);
} catch (MauvaisTypeMessageException e) {
}
remoteUsers.remove(index);
response.setContentType( "text/html" );
PrintWriter out = response.getWriter();
printHomePage(out);
out.close();
}
//Permet de dire si on a changé de pseudo (pour les utilisateurs externes)
protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
String id = request.getParameter("id");
String pseudo = request.getParameter("pseudo");
int index = getIndexByID(id);
response.setContentType( "text/html" );
PrintWriter out = response.getWriter();
//Si le pseudo est déjà pris : génère du html pour en informer l'utilisateur
if (comUDP.containsUserFromPseudo(pseudo)||remoteContainsUserFromPseudo(pseudo)) {
printErrorUsedPseudo(out);
}
else {
Utilisateur user = remoteUsers.get(index);
user.setPseudo(pseudo);
try {
snotify(new MessageSysteme(Message.TypeMessage.INFO_PSEUDO, pseudo, id, 3334), user);
} catch (MauvaisTypeMessageException e) {
}
printActiveUsersOnly(out);
}
out.close();
}
//Méthode générale, embranche vers l'une au l'autre des méthodes HTTP (à défaut d'une "vraie" interface)
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
try {
String type= request.getParameter("type");
switch(type) {
case "POST" :
doPost(request, response);
break;
case "DELETE" :
doDelete(request, response);
break;
case "PUT" :
doPut(request, response);
break;
case "GET" :
//Met à jour la liste des utilisateurs affichée
PrintWriter out1 = response.getWriter();
printActiveUsersOnly(out1);
out1.close();
//génère une jolie page
default :
response.setContentType( "text/html" );
PrintWriter out = response.getWriter();
printErrorInvalidRequest(out);
out.close();
}
}
//Si pas d'argument type : page d'accueil
catch (java.lang.NullPointerException e) {
response.setContentType( "text/html" );
PrintWriter out = response.getWriter();
printHomePage(out);
out.close();
}
}
// ----- OBSERVER METHOD ----- //
@Override
//Note : on part du principe que pour les communications TCP et autres, le serveur agira comme un proxy et donc que les
//utilisateurs externes n'ont pas besoin de connaitre les ip internes des machines
public void update(Object o, Object arg) {
// pour transmettre aux utilisateurs externes les modifications internes
if (arg instanceof MessageSysteme) {
MessageSysteme message = (MessageSysteme) arg;
snotify(message, comUDP.getUserFromID(message.getId()));
}
//pour transmettre la liste des utilisateurs externes aux nouveaux arrivants internes
if (arg instanceof Integer) {
int port = (int)arg;
for (Utilisateur u : remoteUsers) {
try {
comUDP.sendMessage(new MessageSysteme(Message.TypeMessage.INFO_PSEUDO, u.getPseudo(), u.getId(), u.getPort()), port);
} catch (MauvaisTypeMessageException e) {
}
}
}
}
}

View file

@ -0,0 +1,115 @@
package main;
import java.io.Serializable;
import java.net.*;
public class Utilisateur implements Serializable{
private static final long serialVersionUID = 1L;
private String id;
private String pseudo;
private InetAddress ip;
private int port;
//Represents the user that is currently using the application
private static Utilisateur self;
/**
* Create and initialize an object representing an user
*
* @param id : user id as String
* @param pseudo : name under which other users can see this user as String
* @param ip : ip of the device this user is currently using as InetAddress
* @param port : on local mode, port used for the TCP listen socket as int
*
*/
public Utilisateur(String id, String pseudo, InetAddress ip, int port) throws UnknownHostException {
this.id = id;
this.pseudo = pseudo;
this.ip = ip;
this.port = port;
}
// ----- GETTERS ----- //
/**
* Returns user id as String
*
* @return user id as String
*/
public String getId() {
return id;
}
/**
* Returns user pseudo as String
*
* @return user pseudo as String
*/
public String getPseudo() {
return pseudo;
}
/**
* Returns user device's ip as String
*
* @return user device's ip as String
*/
public InetAddress getIp() {
return ip;
}
/**
* Returns the port the user uses for their TCP listen socket as int
*
* @return TCP listen socket port as int
*/
public int getPort() {
return port;
}
/**
* Returns the user currently using this instance of the application as Utilisateur
*
* @return current user as Utilisateur
*/
public static Utilisateur getSelf() {
return Utilisateur.self;
}
// ----- SETTERS ----- //
/**
* Change the pseudo used by an user
*
* @param pseudo : new pseudo as String
*/
public void setPseudo(String pseudo) {
this.pseudo = pseudo;
}
/**
* Sets the self static attribute with a new Utilisateur
*
* @param id : user id as String
* @param pseudo : name under which other users can see this user as String
* @param ip : ip of the device this user is currently using as InetAddress
* @param port : on local mode, port used for the TCP listen socket as int
*/
public static void setSelf(String id, String pseudo, String host, int port) throws UnknownHostException {
if(Utilisateur.self == null) {
Utilisateur.self = new Utilisateur(id, pseudo, InetAddress.getByName(host), port);
}
}
/**
* Sets the self static attribute with null
*/
public static void resetSelf() {
Utilisateur.self = null;
}
}

View file

@ -0,0 +1,8 @@
package messages;
public class MauvaisTypeMessageException extends Exception {
private static final long serialVersionUID = 1L;
}

View file

@ -0,0 +1,130 @@
package messages;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public abstract class Message implements Serializable {
public enum TypeMessage {JE_SUIS_CONNECTE, JE_SUIS_DECONNECTE, INFO_PSEUDO, TEXTE, IMAGE, FICHIER, FICHIER_INIT, FICHIER_ANSWER}
protected TypeMessage type;
private String dateMessage;
private String sender;
private static final long serialVersionUID = 1L;
// ------- GETTERS ------ //
/**
* Returns the current date and time as a string using DateTimeFormatter and LocalDateTime
*
* @return date and time as a String
*/
public static String getDateAndTime() {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
LocalDateTime now = LocalDateTime.now();
return dtf.format(now);
}
/**
* Returns the type of the message
*
* @return message type as TypeMessage
*/
public TypeMessage getTypeMessage() {
return this.type;
}
/**
* Returns the date and time to which the message was timestamped
*
* @return date and time of timestamp as String
*/
public String getDateMessage() {
return this.dateMessage;
}
/**
* Returns the sender of the message (used in the database)
*
* @return sender of message as String
*/
public String getSender() {
return this.sender ;
}
// ------ SETTERS ------ //
/**
* Set the date of the message to a specific timestamp
*
* @param timestamp as (formatted) String
*/
public void setDateMessage(String dateMessage) {
this.dateMessage = dateMessage;
}
/**
* Set the sender of the message to a specified string
*
* @param sender pseudo as String
*/
public void setSender(String sender) {
this.sender = sender;
}
// ----- MESSAGE-STRING CONVERSION METHODS -------//
/**
* Returns a string representing the formatted list of attributes
*
*@return attributes as a String
*/
protected abstract String attributsToString();
/**
* Returns the message as a formatted string
*
*@return message as a String
*/
public String toString() {
return this.type+"###"+this.attributsToString();
}
/**
* Static method. Returns a message obtainer by parsing a given string
*
*@param String representing a message
*@return Message
*/
public static Message stringToMessage(String messageString) {
try {
String[] parts = messageString.split("###");
switch (parts[0]) {
case "JE_SUIS_CONNECTE" :
return new MessageSysteme(TypeMessage.JE_SUIS_CONNECTE);
case "JE_SUIS_DECONNECTE" :
return new MessageSysteme(TypeMessage.JE_SUIS_DECONNECTE, parts[1], parts[2], Integer.parseInt(parts[3]) );
case "INFO_PSEUDO" :
return new MessageSysteme(TypeMessage.INFO_PSEUDO, parts[1], parts[2], Integer.parseInt(parts[3]) );
case "TEXTE" :
return new MessageTexte(TypeMessage.TEXTE, parts[1]);
case "IMAGE" :
return new MessageFichier(TypeMessage.IMAGE, parts[1], parts[2]);
case "FICHIER" :
return new MessageFichier(TypeMessage.FICHIER, parts[1], parts[2]);
}
} catch (MauvaisTypeMessageException e) {}
return null;
}
}

View file

@ -0,0 +1,82 @@
package messages;
public class MessageFichier extends Message {
private static final long serialVersionUID = 1L;
private String contenu;
private String extension;
/**
* Create a file message. These message are used for all interactions regarding file transfer.
*
* The "FICHIER_INIT" messages are used to inform the recipient application that you wish to transfer them files.
* The "FICHIER_ANSWER" messages are answers to "FICHIER_INIT" messages. They indicate that you are ready to receive the file.
* The "contenu" argument then contains the port on which you wish to receive the files.
*
* The "FICHIER" messages contains the files themselves.
* The "IMAGE" messages contains images files, which means the application will display a thumbnail for the image to the recipient.
*
* @param TypeMessage type (must be FICHIER_INIT, FICHIER_ANSWER, FICHIER or IMAGE, else an error is raised)
* @param contenu : message content as String
* @param extension : file extension as string
*
* @throws MauvaisTypeMessageException
*/
public MessageFichier(TypeMessage type, String contenu, String extension) throws MauvaisTypeMessageException{
if ((type==TypeMessage.IMAGE)||(type==TypeMessage.FICHIER) ||(type==TypeMessage.FICHIER_INIT) || (type==TypeMessage.FICHIER_ANSWER) ) {
this.type=type;
this.contenu=contenu;
this.extension=extension;
this.setDateMessage(Message.getDateAndTime());
}
else throw new MauvaisTypeMessageException();
}
// ----- GETTERS ----- //
/**
* Returns content of the message
*
* @return content as String
*/
public String getContenu() {
return this.contenu;
}
/**
* Returns extension of the file contained in the message (if the message contains a file)
*
* @return extension as String
*/
public String getExtension() {
return this.extension;
}
// ----- MESSAGE-STRING CONVERSION METHODS -------//
/**
* Implements attributsToString method of Message
*
* @return attributes as a String
*/
@Override
protected String attributsToString() {
return this.contenu+"###"+this.extension;
}
public String toString() {
if(this.type == TypeMessage.IMAGE) {
return this.contenu;
}else {
String suffixe;
if(this.getSender().equals("Moi")) {
suffixe = "envoyé\n";
}else {
suffixe = "reçu\n";
}
return "<"+this.getDateMessage()+"> : "+this.contenu+" "+suffixe;
}
}
}

View file

@ -0,0 +1,96 @@
package messages;
public class MessageSysteme extends Message {
private static final long serialVersionUID = 1L;
private String pseudo;
private String id;
private int port;
// ------ CONSTRUCTORS ------ //
/**
* Create a system message. These message are used for all system interactions by the UDP channel.
* The "JE_SUIS_CONNECTE" messages are used to inform the network that you just joined.
* They are sent directly after an user log in, and await multiple "INFO_PSEUDO" messages as answers, to build the table of users logged in.
*
* @param TypeMessage type (must be JE_SUIS_CONNECTE, else an error is raised)
* @throws MauvaisTypeMessageException
*/
public MessageSysteme(TypeMessage type) throws MauvaisTypeMessageException{
if (type==TypeMessage.JE_SUIS_CONNECTE) {
this.type=type;
this.pseudo="";
this.id="";
this.port = -1;
}
else throw new MauvaisTypeMessageException();
}
/**
* Create a system message. These message are used for all system interactions by the UDP channel.
* The "JE_SUIS_DECONNECTE" messages are used to inform the network that you just quit it.
*
* The "INFO_PSEUDO" are used to give informations about you to another user, much like an business card.
* They are used either as an answer to a "JE_SUIS_CONNECTE" message or to inform the network of a change of pseudo.
*
* @param TypeMessage type (must be JE_SUIS_DECONNECTE or INFO_PSEUDO, else an error is raised)
* @param pseudo : user pseudo as String
* @param id : user id as String
* @param port : "server" UDP port used by the application (used when the application id in local mode)
*
* @throws MauvaisTypeMessageException
*/
public MessageSysteme(TypeMessage type, String pseudo, String id, int port) throws MauvaisTypeMessageException {
if (type==TypeMessage.INFO_PSEUDO ||(type==TypeMessage.JE_SUIS_DECONNECTE)) {
this.type=type;
this.pseudo=pseudo;
this.id=id;
this.port = port;
}
else throw new MauvaisTypeMessageException();
}
// ----- GETTERS ----- //
/**
* Returns pseudo of the sender of the message (when type == INFO_PSEUDO)
*
* @return user pseudo as String
*/
public String getPseudo() {
return this.pseudo;
}
/**
* Returns id of the sender of the message (when type == INFO_PSEUDO)
*
* @return user id as String
*/
public String getId() {
return this.id;
}
/**
* Returns the "server" UDP port used by the sender of the message
*
* @return port as integer
*/
public int getPort() {
return this.port;
}
// ----- MESSAGE-STRING CONVERSION METHODS -------//
/**
* Implements attributsToString method of Message
*
* @return attributes as a String
*/
@Override
protected String attributsToString() {
return this.pseudo+"###"+this.id+"###"+this.port;
}
}

View file

@ -0,0 +1,55 @@
package messages;
public class MessageTexte extends Message {
private static final long serialVersionUID = 1L;
private String contenu;
/**
* Create a text message. These message are used for basic text conversation via TCP.
*
* @param TypeMessage type (must be TEXT, else an error is raised)
* @param contenu : message content as String
*
* @throws MauvaisTypeMessageException
*/
public MessageTexte(TypeMessage type, String contenu) throws MauvaisTypeMessageException{
if (type==TypeMessage.TEXTE) {
this.type=type;
this.contenu=contenu;
this.setDateMessage(Message.getDateAndTime());
}
else throw new MauvaisTypeMessageException();
}
// ----- GETTERS ----- //
/**
* Returns content of the message
*
* @return content as String
*/
public String getContenu() {
return this.contenu;
}
// ----- MESSAGE-STRING CONVERSION METHODS -------//
/**
* Implements attributsToString method of Message
*
* @return attributes as a String
*/
@Override
protected String attributsToString() {
return this.contenu;
}
@Override
public String toString() {
return "<"+this.getDateMessage()+"> "+this.getSender()+" : "+this.contenu+"\n";
}
}

View file

@ -1,20 +1,3 @@
# Projet_COO_POO
Projet de 4ème année : Conception et programmation orientée objet d'un systeme de clavardage distribué interactif multi-utilisateur temps réel
Contenu (branche master):
Dossier rapports : rapports de conception et de projet
Dossier application : archives .jar permettant d'exécuter l'application (version classique)
Dossier serveur_presence : archives .jar et .war permettant d'exécuter respectivement l'application (version compatible avec le serveur) et le serveur de présence
Pour récupérer les codes sources :
- De l'application classique : branche application
- Du serveur et de l'application modifiée : branche serveur_presence (l'application est dans le projet POO, le servlet dans le projet POO_Server)
Toutes les autres branches concernent des versions obsolètes de l'application, merci de ne pas en tenir compte.

Binary file not shown.

Binary file not shown.

BIN
database0.db Normal file

Binary file not shown.

BIN
javax.servlet-api-3.1.0.jar Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

0
modelisation/dossmod.txt Normal file
View file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.