Compare commits

..

25 commits

Author SHA1 Message Date
Cavailles Kevin
a7cf0debe3 javadoc standard + minor fix 2021-02-16 01:43:36 +01:00
Cavailles Kevin
a7e1991a81 fix warnings, typos, resource leaks 2021-02-15 20:35:47 +01:00
m-gues
9412bb899d nettoyage du code fini 2021-02-15 01:38:07 +01:00
m-gues
ce26d1673b javadoc application main+connexion fini 2021-02-14 17:22:32 +01:00
m-gues
a6097ca49a javadoc connexion en cours 2 2021-02-13 17:21:00 +01:00
m-gues
fca17bbe08 javadoc connexion en cours 2021-02-13 00:37:28 +01:00
m-gues
df7fb8c542 javadoc package messages ok 2021-02-09 01:20:25 +01:00
Cavailles Kevin
dce7df5494 packages database,session cleaned and commented + minor revision communication 2021-02-08 12:41:38 +01:00
Cavailles Kevin
15a618ec50 packages communication,observers cleaned and commented 2021-02-03 09:36:28 +01:00
Cavailles Kevin
0dcfca4937 source files for the broadcast version 2021-01-31 16:33:59 +01:00
Cavailles Kevin
c33e75add7 local v finished, connexion GUI and switch b/w co and std improved 2021-01-31 12:52:49 +01:00
Cavailles Kevin
d7444342dc merge std+session avec appli. Passwords, database, session, fileTransfer 2021-01-25 01:33:53 +01:00
Cavailles Kevin
5ef72d95db merge connexion+standard+session 2020-12-21 19:49:03 +01:00
Cavailles Kevin
08a801f077 communication static -> private 2020-12-21 17:01:13 +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
Cavailles Kevin
bed2d47efa initial commit standard+session 2020-12-09 10:43:48 +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
78 changed files with 8076 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.toLowerCase())) {
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,100 @@
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,172 @@
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};
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("Nom d'utilisateur 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) {
}
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,865 @@
package database;
import java.awt.Dimension;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
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 javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
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();
prepStmt.close();
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();
prepStmt.close();
}
// -------------- 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);
}
}
res.close();
prepStmt.close();
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();
int ret = -1;
if (res.next()) {
ret = res.getInt("id");
}
res.close();
prepStmt.close();
return ret;
}
/**
* 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();
int ret = -1;
if (res.next()) {
ret = res.getInt("id_conversation");
}
res.close();
prepStmt.close();
return ret;
}
/**
* 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();
IvParameterSpec ret = null;
if (res.next()) {
ret = new IvParameterSpec(res.getBytes("iv_conversation"));
}
res.close();
prepStmt.close();
return ret;
}
/**
* 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();
int ret = -1;
if (res.next()) {
ret = res.getInt("id_type");
}
res.close();
prepStmt.close();
return ret;
}
/**
* 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();
String ret = "";
if (res.next()) {
ret = res.getString("label");
}
res.close();
prepStmt.close();
return ret;
}
// -------------- 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 String 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();
prepStmt.close();
this.closeConnection();
return "Utilisateur crée\n";
} catch (SQLException e) {
this.closeConnection();
return "Nom d'utilisateur déjà pris\n";
}
}
/**
* 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);
res.close();
prepStmt.close();
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) {
SQLiteManager sqlManager = new SQLiteManager(0);
JTextArea text = new JTextArea();
text.setEditable(false);
text.setPreferredSize(new Dimension(200,500));
JScrollPane scroll = new JScrollPane(text);
JFrame frame = new JFrame("création d'utilisateur");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(scroll);
frame.setVisible(true);
frame.pack();
File credentials = new File("../credentials.txt");
if(!credentials.exists()) {
try {
credentials.createNewFile();
BufferedWriter writer = new BufferedWriter(new FileWriter(credentials));
writer.write("//Ce fichier est utilisé lors de la création de nouveaux utilisateurs\n"
+ "//Un utilisateur est défini par un nom d'utilisateur unique (sensible à la casse) et un mot de passe.\n"
+ "//Le format à utiliser pour que l'application fonctionne est le suivant :\n"
+ "//[nom_utilisateur]:[mot_de_passe].\n"
+ "//Une seul déclaration d'utilisateur par ligne.\n"
+ "//Afin d'éviter tout problème, chaque ligne qui n'est pas une déclaration d'utilisateur doit commencer par \"//\" \n"
+ "//\n"
+ "//LISTE DES UTILISATEURS A CREER :\n"
+ "//\n");
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
BufferedReader reader = new BufferedReader(new FileReader(credentials));
String line = reader.readLine();
while(line != null){
line = line.trim();
if(line.charAt(0) != '/' && line.charAt(0) != '\n') {
String[] data = line.split(":");
String username = data[0];
String pwd = data[1];
String res = sqlManager.createNewUserEncrypt(username, pwd);
text.append(res);
}
line = reader.readLine();
}
reader.close();
text.append("\nFini !\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}

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,346 @@
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) {
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) {
}
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) {
}
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) {
}
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,314 @@
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) {
}
}
/**
* 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) {
}
}
/**
* 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) {
}
}
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,327 @@
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.ObserverUserList;
import session.VueSession;
public class ControleurStandard
implements ActionListener, ListSelectionListener, WindowListener, ObserverInputMessage, ObserverUserList {
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;
// Instruction to avoid closing the application when clicking the upper right
// cross
this.vue.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
this.vueConnexion = vueConnexion;
// The TCP server waiting for session requests
this.tcpServ = new TCPServer(portServerTCP);
this.tcpServ.addObserver(this);
this.tcpServ.start();
// The UDP communication (server + userlist manager)
this.commUDP = commUDP;
// An array to store the usernames of the users a session exists at any point in
// time.
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) {
}
}
}
list.clearSelection();
System.out.println("pseudo de la personne a atteindre : " + pseudoOther);
}
}
// ---------- ACTION LISTENER OPERATIONS ----------//
@Override
public void actionPerformed(ActionEvent e) {
// Case change 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) {
}
} else {
this.vue.setDisplayedPseudo(this.lastPseudo);
}
modifierPseudo.setText("Modifier");
this.modifPseudo = ModifPseudo.TERMINE;
}
this.vue.toggleEditPseudo();
}
// Case logging off
else if ((JButton) e.getSource() == this.vue.getButtonDeconnexion()) {
try {
this.setVueConnexion();
} catch (IOException e1) {
}
}
// Case close session
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) {
}
}
@Override
public void windowOpened(WindowEvent e) {
}
@Override
public void windowClosed(WindowEvent e) {
}
@Override
public void windowIconified(WindowEvent e) {
}
@Override
public void windowDeiconified(WindowEvent e) {
}
@Override
public void windowActivated(WindowEvent e) {
}
@Override
public void windowDeactivated(WindowEvent e) {
}
// ------------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------------- //
// Method called when there is a connection on the TCP server
// This always means, in theory, that an user is asking to create a session
@Override
public void updateInput(Object o, Object arg) {
if (o == this.tcpServ) {
// TCP socket given from the TCP Server
Socket sockAccept = (Socket) arg;
try {
// Read the other user's pseudo
String pseudoOther = this.readMessage(sockAccept);
String idOther = this.commUDP.getUserFromPseudo(pseudoOther).getId();
int reponse;
// Display the dialog box and wait for replay
if (!this.idsSessionEnCours.contains(idOther)) {
reponse = this.vue.displayJOptionAskForSession(pseudoOther);
System.out.println("reponse : " + reponse);
} else {
reponse = 1;
}
// If the session is accepted
// Create a new VueSession with the socket
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) {
}
}
}
// Method called when the userlist of the CommunicationUDP is updated
@Override
public void updateList(Object o, ArrayList<Utilisateur> userList) {
if (o == this.commUDP) {
// Get every pseudo from the userlist and give the pseudo's list to the view
ArrayList<String> pseudos = new ArrayList<String>();
for (Utilisateur u : userList) {
pseudos.add(u.getPseudo());
}
this.vue.setActiveUsersList(pseudos);
}
}
/**
* Send the system message DECONNECTE. Reset the userlist and the user data.
* Close all sessions. Set this view invisible and the connexion's view visible.
*
* @throws UnknownHostException
* @throws IOException
*/
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);
}
/**
* Set the controler as the observer of the commUDP to receive the updates on
* the userlist. Then send the system message JE_SUIS_CONNECTE and this
* application's user data
*
* @throws UnknownHostException
* @throws IOException
*/
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,463 @@
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>();
/**
* Create the main frame of the application.
*
* @param title
* @param commUDP
* @param portServerTCP
* @param sqlManager
* @param vueConnexion
* @throws IOException
*/
public VueStandard(String title, CommunicationUDP commUDP, int portServerTCP, SQLiteManager sqlManager,
VueConnexion vueConnexion) throws IOException {
super(title);
//An array to keep tracks of the tabbed pane's close buttons
this.tabButtons = new ArrayList<JButton>();
//An array to keep tracks of the sessions' view
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());
// -----------Tabbed Pane for the session's panels -----------//
this.zoneSessions = new JTabbedPane();
this.zoneSessions.setTabPlacement(JTabbedPane.BOTTOM);
this.zoneSessions.setPreferredSize(new Dimension(600, 600));
// --------Panel up left 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 mid left userlist--------//
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 down left log off--------//
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);
}
// --------Add the panels to the frame--------//
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 -------------//
/**
* Set the list of active users to be displayed on the view. First remove the
* old list.
*
* @param users The list of active users.
*/
protected void setActiveUsersList(ArrayList<String> users) {
this.removeAllUsers();
this.userList.addAll(users);
}
/**
* Set the pseudo displayed on the view with the given pseudo.
*
* @param pseudo
*/
protected void setDisplayedPseudo(String pseudo) {
this.pseudoSelf.setText(pseudo);
}
/**
* Set this application's user pseudo.
*/
public void setPseudoSelf() {
this.setDisplayedPseudo(Utilisateur.getSelf().getPseudo());
}
// ------------ JOPTIONS -------------//
/**
* Display the dialog box asking confirmation to create a session.
*
* @param pseudo The other user's pseudo.
* @return The chosen value.
*/
protected int displayJOptionSessionCreation(String pseudo) {
return JOptionPane.showConfirmDialog(this, "Voulez vous créer une session avec " + pseudo + " ?",
"Confirmation session", JOptionPane.YES_NO_OPTION);
}
/**
* Display the dialog box to reply to a session request.
*
* @param pseudo The other user's pseudo.
* @return The chosen value.
*/
protected int displayJOptionAskForSession(String pseudo) {
return JOptionPane.showConfirmDialog(this, pseudo + " souhaite creer une session avec vous.",
"Accepter demande", JOptionPane.YES_NO_OPTION);
}
/**
* Display an informative box with the answer to a session request
* @param reponse
*/
protected void displayJOptionResponse(String reponse) {
JOptionPane.showMessageDialog(this, "Demande de session " + reponse);
}
// ------------ TOGGLE BUTTONS -------------//
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-------------//
/**
* Check if an object belongs to tabButtons.
*
* @param o
* @return
*/
protected boolean isButtonTab(Object o) {
return this.tabButtons.contains(o);
}
/**
* Remove the tab (the session) of the tabbed pane that corresponds
* to the given "close" button.
*
* @param button
* @return
*/
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;
}
/**
* Add a session on the view with the given pseudo as the tab title.
*
* @param pseudo
* @param session
*/
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();
}
/**
* Remove the tab (the session) of the tabbed pane that corresponds
* to the given session's panel (view).
*
* @param vue
* @return
*/
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

@ -1,20 +1,3 @@
# Projet_COO_POO # 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 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.

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: 77 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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,41 @@
package communication.filetransfer;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import observers.ObserverInputMessage;
public class FileTransferClient {
private int portOther;
private InetAddress addrOther;
private ArrayList<File> files = null;
private ObserverInputMessage obsInput;
public FileTransferClient(InetAddress addrOther, int portOther, ArrayList<File> filesToSend, ObserverInputMessage obs) throws UnknownHostException, IOException {
this.addrOther = addrOther;
this.portOther = portOther;
this.files = filesToSend;
this.obsInput = obs;
}
public void sendFiles() throws IOException, InterruptedException {
for(File f: this.files) {
FileTransferSendingThread ftc = new FileTransferSendingThread(this.addrOther, this.portOther, f,this.obsInput);
ftc.start();
ftc.join();
}
}
}

View file

@ -0,0 +1,82 @@
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;
public FileTransferReceivingThread(SocketChannel sock, ObserverInputMessage obs) {
this.sockTransfert = sock;
this.obsInput = obs;
}
public void run() {
try {
int nbByteRead = 0;
ByteBuffer fileData = ByteBuffer.allocate(4 * FileTransferUtils.KB_SIZE);
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]);
FileOutputStream fOutStream = new FileOutputStream(filePath);
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();
Message mUpdate = FileTransferUtils.processMessageToDisplay(new File(filePath));
mUpdate.setSender("other");
this.obsInput.update(this, mUpdate);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
this.sockTransfert.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private String[] processFileInformation(MessageFichier m) {
return m.getContenu().split(";");
}
}

View file

@ -0,0 +1,75 @@
package communication.filetransfer;
import java.io.File;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
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;
public FileTransferSendingThread(InetAddress addrOther, int port, File fileToSend, ObserverInputMessage obs) throws IOException {
SocketChannel sock = SocketChannel.open();
SocketAddress addr = new InetSocketAddress(addrOther, port);
sock.connect(addr);
this.sockTransfert = sock;
this.file = fileToSend;
this.obsInput = obs;
}
public void run() {
try {
ByteBuffer fileData = ByteBuffer.allocate(4 * FileTransferUtils.KB_SIZE);
ObjectOutputStream outputFileInformation = new ObjectOutputStream(
this.sockTransfert.socket().getOutputStream());
FileChannel fileReader = FileChannel.open(Paths.get(file.getPath()));
String str = file.getName() + ";" + file.getTotalSpace();
// Send file datas (name + size);
outputFileInformation.writeObject(new MessageFichier(TypeMessage.FICHIER, str, ""));
while (fileReader.read(fileData) > 0) {
fileData.flip();
this.sockTransfert.write(fileData);
fileData.clear();
}
fileReader.close();
Message mUpdate = FileTransferUtils.processMessageToDisplay(this.file);
mUpdate.setSender("Moi");
this.obsInput.update(this, mUpdate);
} catch (IOException | MauvaisTypeMessageException e) {
e.printStackTrace();
} finally {
try {
this.sockTransfert.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

View file

@ -0,0 +1,46 @@
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;
public FileTransferServer(int nbFile, ObserverInputMessage obs) throws UnknownHostException, IOException {
this.sockFTListen = ServerSocketChannel.open();
this.sockFTListen.socket().bind(new InetSocketAddress(0));
this.nbFile = nbFile;
this.obsInput = obs;
}
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,120 @@
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 {
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;
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);
return new MessageFichier(type, contenu, extension);
} catch (MauvaisTypeMessageException e) {
System.out.println("Should never go in");
e.printStackTrace();
}
return null;
}
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;
}
private static BufferedImage createThumbnail(BufferedImage image){
float w = image.getWidth();
float ratio = (w > 150) ? (150F/w) : 1;
BufferedImage scaled = scale(image, ratio);
return scaled;
}
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;
}
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;
}
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;
}
public static void createDownloads() {
File downloads = new File(FileTransferUtils.DOWNLOADS_RELATIVE_PATH);
if(!downloads.exists()) {
downloads.mkdir();
}
}
}

View file

@ -0,0 +1,73 @@
package communication.tcp;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
import observers.ObserverInputMessage;
import observers.ObserverSocketState;
import messages.MauvaisTypeMessageException;
import messages.Message;
public class TCPClient {
private Socket sockTCP;
private ObjectOutputStream output;
private TCPInputThread inputThread;
public TCPClient(Socket sockTCP) throws IOException {
this.sockTCP = sockTCP;
this.output = new ObjectOutputStream(sockTCP.getOutputStream());
ObjectInputStream input = new ObjectInputStream(sockTCP.getInputStream());
this.inputThread = new TCPInputThread(input);
}
public TCPClient(InetAddress addr) throws IOException {
this(new Socket(addr, TCPServer.PORT_SERVER));
}
public void startInputThread() {
this.inputThread.start();
}
public void sendMessage(Message message) throws IOException, MauvaisTypeMessageException {
System.out.println("dans write");
this.output.writeObject(message);
}
public void setObserverInputThread(ObserverInputMessage o) {
this.inputThread.setObserverInputMessage(o);
}
public void setObserverSocketState(ObserverSocketState o) {
this.inputThread.setObserverSocketState(o);
}
public void destroyAll() {
try {
if (!this.sockTCP.isClosed()) {
this.output.close();
this.sockTCP.close();
this.inputThread.setObserverSocketState(null);
}
this.inputThread = null;
this.sockTCP = null;
this.output = null;
} catch (Exception e) {
e.printStackTrace();
}
}
}

View file

@ -0,0 +1,72 @@
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;
public TCPInputThread(ObjectInputStream input) {
this.input = input;
this.running = true;
}
@Override
public void run() {
while (this.running) {
try {
// System.out.println("dans read");
Object o = this.input.readObject();
this.obsInput.update(this, o);
} catch (IOException | ClassNotFoundException e) {
this.interrupt();
}
}
}
@Override
public void interrupt() {
try {
//Stop the thread
this.running = false;
//Close the stream and the socket
this.input.close();
if(this.obsState != null) {
//Send an update to the controller
this.obsState.updateSocketState(this, true);
}
//Set every attribute to null so they're collected by the GC
this.obsInput = null;
this.obsState = null;
this.input = null;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
protected void setObserverInputMessage(ObserverInputMessage o) {
this.obsInput = o;
}
protected void setObserverSocketState(ObserverSocketState o) {
this.obsState = o;
}
}

View file

@ -0,0 +1,42 @@
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 {
public static int PORT_SERVER = 7000;
private ServerSocket sockListenTCP;
private ObserverInputMessage obs;
public TCPServer() throws UnknownHostException, IOException {
this.sockListenTCP = new ServerSocket(PORT_SERVER, 50, InetAddress.getLocalHost());
}
@Override
public void run() {
System.out.println("TCP running");
Socket sockAccept;
while(true) {
try {
sockAccept = this.sockListenTCP.accept();
this.obs.update(this, sockAccept);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void addObserver(ObserverInputMessage obs) {
this.obs = obs;
}
}

View file

@ -0,0 +1,176 @@
package communication.udp;
import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import main.Utilisateur;
import messages.*;
import observers.ObserverUserList;
public class CommunicationUDP extends Thread {
protected static int PORT_SERVEUR = 3000;
protected static int PORT_CLIENT = 2000;
private UDPClient client;
private UDPServer server;
private int portServer;
private ArrayList<Utilisateur> users = new ArrayList<Utilisateur>();
private ObserverUserList observer;
public CommunicationUDP() throws SocketException, UnknownHostException {
this.portServer = PORT_SERVEUR;
this.server = new UDPServer(portServer, this);
this.server.start();
this.client = new UDPClient(PORT_CLIENT);
}
public void setObserver(ObserverUserList obs) {
this.observer = obs;
}
// -------------- USER LIST UPDATE FUNCTION --------------//
protected synchronized void addUser(String idClient, String pseudoClient, InetAddress ipClient)
throws IOException {
users.add(new Utilisateur(idClient, pseudoClient, ipClient));
this.sendUpdate();
}
protected synchronized void changePseudoUser(String idClient, String pseudoClient, InetAddress ipClient) {
int index = getIndexFromID(idClient);
users.get(index).setPseudo(pseudoClient);
this.sendUpdate();
}
protected synchronized void removeUser(String idClient, String pseudoClient, InetAddress ipClient) {
int index = getIndexFromIP(ipClient);
if (index != -1) {
users.remove(index);
}
this.sendUpdate();
}
public void removeAll() {
int oSize = users.size();
for (int i = 0; i < oSize; i++) {
users.remove(0);
}
}
// -------------- 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().toLowerCase().equals(pseudo)) {
return true;
}
}
return false;
}
// -------------- GETTERS --------------//
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;
}
private int getIndexFromID(String id) {
for (int i = 0; i < users.size(); i++) {
if (users.get(i).getId().equals(id)) {
return i;
}
}
return -1;
}
private int getIndexFromIP(InetAddress ip) {
for (int i = 0; i < users.size(); i++) {
if (users.get(i).getIp().equals(ip)) {
return i;
}
}
return -1;
}
// -------------- SEND MESSAGES --------------//
public void sendMessageConnecte() throws UnknownHostException, IOException {
try {
MessageSysteme m = new MessageSysteme(Message.TypeMessage.JE_SUIS_CONNECTE);
this.client.sendMessageUDP_broadcast(m);
} catch (MauvaisTypeMessageException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void sendMessageInfoPseudo(InetAddress addrOther) {
Utilisateur self = Utilisateur.getSelf();
try {
MessageSysteme m = new MessageSysteme(Message.TypeMessage.INFO_PSEUDO, self.getPseudo(), self.getId());
this.client.sendMessageUDP_local(m, addrOther);
} catch (MauvaisTypeMessageException | IOException e) {
e.printStackTrace();
}
}
public void sendMessageInfoPseudo() {
Utilisateur self = Utilisateur.getSelf();
try {
MessageSysteme m = new MessageSysteme(Message.TypeMessage.INFO_PSEUDO, self.getPseudo(), self.getId());
this.client.sendMessageUDP_broadcast(m);
} catch (MauvaisTypeMessageException | IOException e) {
e.printStackTrace();
}
}
public void sendMessageDelete() {
try {
MessageSysteme m = new MessageSysteme(Message.TypeMessage.JE_SUIS_DECONNECTE);
this.client.sendMessageUDP_broadcast(m);
} catch (MauvaisTypeMessageException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void sendUpdate() {
if(this.observer != null) {
this.observer.updateList(this, users);
}
}
public void destroyAll() {
this.client.destroyAll();
this.server.interrupt();
}
}

View file

@ -0,0 +1,46 @@
package communication.udp;
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;
public UDPClient(int port) throws SocketException, UnknownHostException {
this.sockUDP = new DatagramSocket(port);
InetAddress localHost = InetAddress.getLocalHost();
NetworkInterface networkInterface = NetworkInterface.getByInetAddress(localHost);
this.broadcast = networkInterface.getInterfaceAddresses().get(0).getBroadcast();
System.out.println(this.broadcast);
System.out.println(InetAddress.getLocalHost());
}
protected void sendMessageUDP_local(Message message, InetAddress addrOther) throws IOException {
String messageString= message.toString();
DatagramPacket outpacket = new DatagramPacket(messageString.getBytes(), messageString.length(), addrOther, CommunicationUDP.PORT_SERVEUR);
this.sockUDP.send(outpacket);
}
protected void sendMessageUDP_broadcast(Message message) throws IOException{
String messageString = message.toString();
DatagramPacket outpacket = new DatagramPacket(messageString.getBytes(), messageString.length(), this.broadcast, CommunicationUDP.PORT_SERVEUR);
this.sockUDP.send(outpacket);
}
protected void destroyAll() {
this.sockUDP.close();
this.sockUDP = null;
this.broadcast = null;
}
}

View file

@ -0,0 +1,87 @@
package communication.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import main.Utilisateur;
import messages.*;
public class UDPServer extends Thread {
private DatagramSocket sockUDP;
private CommunicationUDP commUDP;
private byte[] buffer;
private boolean running;
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 {
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);
if (inPacket.getAddress().equals(InetAddress.getLocalHost())) {
continue;
}
switch (msg.getTypeMessage()) {
case JE_SUIS_CONNECTE:
if (Utilisateur.getSelf() != null) {
this.commUDP.sendMessageInfoPseudo(inPacket.getAddress());
}
break;
case INFO_PSEUDO:
MessageSysteme m = (MessageSysteme) msg;
if (this.commUDP.containsUserFromID(m.getId())) {
this.commUDP.changePseudoUser(m.getId(), m.getPseudo(), inPacket.getAddress());
} else {
this.commUDP.addUser(m.getId(), m.getPseudo(), inPacket.getAddress());
System.out.println(m.getId() + ", " + m.getPseudo());
}
break;
case JE_SUIS_DECONNECTE:
MessageSysteme m2 = (MessageSysteme) msg;
this.commUDP.removeUser(m2.getId(), m2.getPseudo(), inPacket.getAddress());
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");
}
}
}
@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,142 @@
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{
private enum Etat {DEBUT, ID_OK};
private VueConnexion vue;
private Etat etat;
private CommunicationUDP comUDP;
private String username;
private SQLiteManager sqlManager;
private VueStandard vueStd;
public ControleurConnexion(VueConnexion vue) {
this.vue = vue;
this.etat = Etat.DEBUT;
this.username = "";
this.sqlManager = new SQLiteManager(0);
this.vueStd = null;
//Pour les tests, changer pour un truc plus general quand on change CommunicationUDP
try {
this.comUDP = new CommunicationUDP();
} catch (IOException e) {
e.printStackTrace();
}
}
@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) {
e2.printStackTrace();
}
if (inputOK) {
this.etat=Etat.ID_OK;
//Envoi broadcast du message "JeSuisActif" et, attente du retour de la liste des utilisateurs actifs
try {
comUDP.sendMessageConnecte();
} catch (UnknownHostException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
Thread.sleep(2);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
//Mise en place de la demande du pseudo
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();
//Recherche dans la liste locale des utilisateurs connectes, report sur inputOK
inputOK = !this.comUDP.containsUserFromPseudo(pseudo);
if(pseudo.equals("")) {
this.vue.setConnexionInfo("Votre pseudonyme doit contenir au moins 1 caratère");
}else if (inputOK) {
//Reglage de l'utilisateur
try {
Utilisateur.setSelf(this.username, pseudo, "localhost");
} catch (UnknownHostException e2) {
// TODO Auto-generated catch block
e2.printStackTrace();
}
this.comUDP.sendMessageInfoPseudo();
try {
this.resetView();
this.vue.setVisible(false);
this.setVueStandard();
} catch (IOException e1) {
e1.printStackTrace();
}
}
else this.vue.setConnexionInfo("Ce nom est déjà utilisé, veuillez en choisir un autre");
}
}
private void setVueStandard() throws IOException {
if(this.vueStd == null) {
this.vueStd = new VueStandard("Standard", this.comUDP, this.sqlManager, this.vue);
}else {
this.vueStd.initControleur();
this.vueStd.setPseudoSelf();
this.vueStd.setVisible(true);
}
}
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,123 @@
package connexion;
//Importe les librairies
import java.awt.*;
import javax.swing.*;
import main.Vue;
public class VueConnexion extends Vue {
private static final long serialVersionUID = 1L;
//Elements vue
private JButton boutonValider;
private JTextField inputUsername;
private JPasswordField inputPassword;
private JLabel labelUsername;
private JLabel labelPassword;
private JLabel connexionInfo;
private JPanel main;
private JPanel panelPassword;
//Controleur
private ControleurConnexion controle;
//penser à enlever le numtest
public VueConnexion() {
super("Connexion");
controle = new ControleurConnexion(this);
//Creation fenetre
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(400, 300);
this.setLocationRelativeTo(null);
//Ajout elements
ajouterElements();
//Regle le bouton par défaut
this.getRootPane().setDefaultButton(boutonValider);
//Affiche la fenetre
this.setVisible(true);
}
private void ajouterElements() {
//Creation panel
main = new JPanel(new GridLayout(4,1));
JPanel panelUsername = new JPanel(new GridLayout(1, 2));
this.panelPassword = new JPanel(new GridLayout(1, 2));
//Cree les 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");
//Le controleur guette les evenements du bouton
boutonValider.addActionListener(controle);
//Ajoute les elements
panelUsername.add(this.inputUsername);
panelUsername.add(this.labelUsername);
this.panelPassword.add(this.inputPassword);
this.panelPassword.add(this.labelPassword);
main.add(connexionInfo);
main.add(panelUsername);
main.add(this.panelPassword);
main.add(boutonValider);
this.add(main);
}
//Getters et setters
protected void setConnexionInfo(String text) {
this.connexionInfo.setText(text);
}
protected void setTextUsernameField(String text) {
this.labelUsername.setText(text);
}
protected String getUsernameValue() {
return this.inputUsername.getText();
}
protected char[] getPasswordValue() {
return this.inputPassword.getPassword();
}
protected void resetUsernameField() {
this.inputUsername.setText("");
}
protected void removePasswordPanel() {
this.main.remove(2);
}
protected void addPasswordPanel() {
this.main.add(this.panelPassword, 2);
}
protected void resetPasswordField() {
this.inputPassword.setText("");
}
}

View file

@ -0,0 +1,73 @@
package database;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
class SQLiteCreateTables {
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);
}
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_emetteur INTEGER REFERENCES user (id) \r\n"
+ " NOT NULL,\r\n"
+ " id_recepteur INTEGER REFERENCES user (id) \r\n"
+ " NOT NULL,\r\n"
+" iv_conversation BLOB NOT NULL"
+ ");";
Statement stmt = connec.createStatement();
stmt.execute(createTableConversation);
}
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);
}
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,84 @@
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 SQLiteEncprytion {
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";
protected static byte[] getNextSalt() {
byte[] salt = new byte[24];
RANDOM.nextBytes(salt);
return salt;
}
protected static byte[] hash(char[] password, byte[] salt) {
return SQLiteEncprytion.getKey(password, salt).getEncoded();
}
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;
}
public static IvParameterSpec generateIv() {
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
return new IvParameterSpec(iv);
}
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);
}
protected static byte[] decryptByte(String algorithm, byte[] cipherText, 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(cipherText));
return plainText;
}
protected static String decryptString(String algorithm, byte[] cipherText, SecretKey key, IvParameterSpec iv) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
return new String(SQLiteEncprytion.decryptByte(algorithm, cipherText, key, iv) );
}
public static byte[] keyToByte(SecretKey key) {
return Base64.getEncoder().encode(key.getEncoded());
}
public static SecretKey byteToKey(byte[] encodedKey) {
byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
return new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
}
}

View file

@ -0,0 +1,562 @@
package database;
import java.io.File;
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;
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();
File db = new File("../database"+this.numDatabase+".db");
if(db.delete()) {
System.out.println("supp");
}else {
System.out.println("no supp");
}
e.printStackTrace();
}
this.closeConnection();
}
private void openConnection() {
String url = "jdbc:sqlite:"+ DATABASE_RELATIVE_PATH + this.numDatabase + ".db";
try {
this.connec = DriverManager.getConnection(url);
// System.out.println("Connection to bdd established");
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
private void closeConnection() {
try {
if (this.connec != null) {
this.connec.close();
}
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
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);
this.connec.setAutoCommit(false);
for(Message m : messages) {
try {
nbRows += this.insertMessage(idConversation, m, ivConversation);
} catch (SQLException e) {
e.printStackTrace();
this.connec.rollback();
}
}
this.connec.commit();
this.closeConnection();
//System.out.println("Nombre de message(s) insérée(s) : " + nbRows);
return nbRows;
}
public ArrayList<Message> getHistoriquesMessages(String usernameOther, String pseudoOther) throws SQLException {
this.openConnection();
ArrayList<Message> messages = new ArrayList<Message>();
String usernameSelf = Utilisateur.getSelf().getId();
int idSelf = this.getIDUser(usernameSelf);
int idOther = this.getIDUser(usernameOther);
int idConversationSelf = this.getIDConversation(idSelf, idOther);
int idConversationOther = this.getIDConversation(idOther, idSelf);
IvParameterSpec ivConversation = this.getIvConversation(idConversationSelf);
// String str = "datetime(d1,'unixepoch','localtime')";
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();
//Retrieve 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 {
content = this.bytesToStringContent(res.getBytes("content"), ivConversation);
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException
| SQLException e1) {
//System.out.println("erreur déchiffrement");
}
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;
}
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();
}
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;
}
private void insertConversation(int idSender, int idReceiver) throws SQLException {
String insertConversationRequest = "INSERT INTO conversation (id_emetteur, id_recepteur, iv_conversation) " + "VALUES "
+ "(?, ?, ?),"
+ "(?, ?, ?);";
byte[] ivConversation = SQLiteEncprytion.generateIv().getIV();
PreparedStatement prepStmt = this.connec.prepareStatement(insertConversationRequest);
prepStmt.setInt(1, idSender);
prepStmt.setInt(2, idReceiver);
prepStmt.setBytes(3, ivConversation);
prepStmt.setInt(4, idReceiver);
prepStmt.setInt(5, idSender);
prepStmt.setBytes(6, ivConversation);
prepStmt.executeUpdate();
}
private int getIDConversation(int idSender, int idReceiver) throws SQLException {
String getIDRequest = "SELECT id_conversation " + "FROM conversation " + "WHERE id_emetteur = ? "
+ "AND id_recepteur = ? ;";
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;
}
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;
}
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;
}
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 "";
}
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 = SQLiteEncprytion.encrypt(SQLiteEncprytion.encryptAlgorithm, content.getBytes(), this.dbDataKey, iv);
return encryptedContent;
}
private String bytesToStringContent(byte[] encryptedContent, IvParameterSpec iv) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
return SQLiteEncprytion.decryptString(SQLiteEncprytion.encryptAlgorithm, encryptedContent, this.dbDataKey, iv);
}
private String processMessageType(Message m) {
switch (m.getTypeMessage()) {
case TEXTE: return "text";
case FICHIER: return "file";
case IMAGE: return "image";
default: return "";
}
}
private String processExtension(Message m) {
if(m.getTypeMessage() == TypeMessage.TEXTE) {
return null;
}else {
MessageFichier mFile = (MessageFichier) m;
return mFile.getExtension();
}
}
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;
}
public void createNewUserEncrypt(String username, String password) {
String algo = SQLiteEncprytion.encryptAlgorithm;
KeyGenerator keyGen = null;
try {
keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
byte[] passwordSalt = SQLiteEncprytion.getNextSalt();
byte[] dbDataKeySalt = SQLiteEncprytion.getNextSalt();
SecretKey dbDataKey = keyGen.generateKey();
SecretKey dbDataEncryptKey = SQLiteEncprytion.getKey(password.toCharArray(), dbDataKeySalt);
IvParameterSpec ivDbDataKey = SQLiteEncprytion.generateIv();
byte[] passwordHash = SQLiteEncprytion.hash(password.toCharArray(), passwordSalt);
byte[] dbDataKeyEncrypted = null;
byte[] encryptedPasswordHash = null;
try {
dbDataKeyEncrypted = SQLiteEncprytion.encrypt(
algo, SQLiteEncprytion.keyToByte(dbDataKey), dbDataEncryptKey, ivDbDataKey);
encryptedPasswordHash = SQLiteEncprytion.encrypt(
algo, passwordHash , dbDataKey, ivDbDataKey);
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
// TODO Auto-generated catch block
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) {
// TODO Auto-generated catch block
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();
}
/**
*
* @param username
* @param password
* @return -1 if user do not exists,
* 0 if password incorrect,
* 1 if password 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");
byte[] dbDataKeySalt = res.getBytes("db_datakey_salt");
SecretKey dbDataEncryptKey = SQLiteEncprytion.getKey(password, dbDataKeySalt);
IvParameterSpec iv = new IvParameterSpec(res.getBytes("iv_datakey"));
byte[] encryptedDbDataKey = res.getBytes("encrypted_db_datakey");
SecretKey dbDataKey = null;
try {
dbDataKey = SQLiteEncprytion.byteToKey(
SQLiteEncprytion.decryptByte(SQLiteEncprytion.encryptAlgorithm, encryptedDbDataKey, dbDataEncryptKey, iv)
);
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
//System.out.println("Problème déchiffrement clé db");
}
this.dbDataKey = dbDataKey;
byte[] encryptedPasswordHash = res.getBytes("encrypted_pwd_hashsalt");
byte[] passwordHash = SQLiteEncprytion.hash(password, passwordSalt);
this.closeConnection();
boolean checkHash = this.checkHashPwd(passwordHash ,encryptedPasswordHash, dbDataKey, iv);
if(checkHash) {
return 1;
}
return 0;
}
private boolean checkHashPwd(byte[] passwordHash, byte[] encryptedPasswordHash, SecretKey dbDataKey, IvParameterSpec iv) {
byte[] expectedHash = "".getBytes();
try {
expectedHash = SQLiteEncprytion.decryptByte(SQLiteEncprytion.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;
}
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(1);
for(int i=0; i<hardcodedNames.length; i++) {
sqlManager.createNewUserEncrypt(hardcodedNames[i]+i, pwdPrefix+hardcodedNames[i].charAt(0)+i);
}
}
}

37
src_reseau/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();
}
}

View file

@ -0,0 +1,53 @@
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 static Utilisateur self;
public Utilisateur(String id, String pseudo, InetAddress ip) throws UnknownHostException {
this.id = id;
this.pseudo = pseudo;
this.ip = ip;
}
public String getId() {
return id;
}
public String getPseudo() {
return pseudo;
}
public void setPseudo(String pseudo) {
this.pseudo = pseudo;
}
public InetAddress getIp() {
return ip;
}
public static void setSelf(String id, String pseudo, String host) throws UnknownHostException {
if(Utilisateur.self == null) {
Utilisateur.self = new Utilisateur(id, pseudo, InetAddress.getByName(host));
}
}
public static Utilisateur getSelf() {
return Utilisateur.self;
}
public static void resetSelf() {
Utilisateur.self = null;
}
}

19
src_reseau/main/Vue.java Normal file
View file

@ -0,0 +1,19 @@
package main;
import javax.swing.JFrame;
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,96 @@
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, MESSAGE_NUL, FICHIER_INIT, FICHIER_ANSWER}
protected TypeMessage type;
private String dateMessage;
private String sender;
private static final long serialVersionUID = 1L;
public static String getDateAndTime() {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
LocalDateTime now = LocalDateTime.now();
return dtf.format(now);
}
public TypeMessage getTypeMessage() {
return this.type;
}
public void setDateMessage(String dateMessage) {
this.dateMessage = dateMessage;
}
public String getDateMessage() {
return this.dateMessage;
}
public String getSender() {
return this.sender ;
}
public void setSender(String sender) {
this.sender = sender;
}
protected abstract String attributsToString();
public String toString() {
return this.type+"###"+this.attributsToString();
}
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);
case "INFO_PSEUDO" :
return new MessageSysteme(TypeMessage.INFO_PSEUDO, parts[1], parts[2]);
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;
}
//tests ici
public static void main(String[] args) throws MauvaisTypeMessageException {
Message m1 = new MessageSysteme(TypeMessage.JE_SUIS_CONNECTE);
Message m2 = new MessageSysteme(TypeMessage.JE_SUIS_DECONNECTE);
Message m3 = new MessageSysteme(TypeMessage.INFO_PSEUDO, "pseudo156434518", "id236");
Message m4 = new MessageTexte(TypeMessage.TEXTE, "blablabla");
Message m5 = new MessageFichier(TypeMessage.FICHIER, "truc", ".pdf");
System.out.println(Message.stringToMessage(m1.toString()));
System.out.println(Message.stringToMessage(m2.toString()));
System.out.println(Message.stringToMessage(m3.toString()));
System.out.println(Message.stringToMessage(m4.toString()));
System.out.println(Message.stringToMessage(m5.toString()));
}
}

View file

@ -0,0 +1,46 @@
package messages;
public class MessageFichier extends Message {
private static final long serialVersionUID = 1L;
private String contenu;
private String extension;
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();
}
public String getContenu() {
return this.contenu;
}
public String getExtension() {
return this.extension;
}
@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,39 @@
package messages;
public class MessageSysteme extends Message {
private static final long serialVersionUID = 1L;
private String pseudo;
private String id;
public MessageSysteme(TypeMessage type) throws MauvaisTypeMessageException{
if ((type==TypeMessage.JE_SUIS_CONNECTE)||(type==TypeMessage.JE_SUIS_DECONNECTE)||(type==TypeMessage.MESSAGE_NUL)) {
this.type=type;
this.pseudo="";
this.id="";
}
else throw new MauvaisTypeMessageException();
}
public MessageSysteme(TypeMessage type, String pseudo, String id) throws MauvaisTypeMessageException {
if (type==TypeMessage.INFO_PSEUDO) {
this.type=type;
this.pseudo=pseudo;
this.id=id;
}
else throw new MauvaisTypeMessageException();
}
public String getPseudo() {
return this.pseudo;
}
public String getId() {
return this.id;
}
@Override
protected String attributsToString() {
return this.pseudo+"###"+this.id;
}
}

View file

@ -0,0 +1,33 @@
package messages;
public class MessageTexte extends Message {
private static final long serialVersionUID = 1L;
private String contenu;
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();
}
public String getContenu() {
return this.contenu;
}
@Override
protected String attributsToString() {
return this.contenu;
}
@Override
public String toString() {
return "<"+this.getDateMessage()+"> "+this.getSender()+" : "+this.contenu+"\n";
}
}

View file

@ -0,0 +1,5 @@
package observers;
public interface ObserverInputMessage {
public void update(Object o, Object arg);
}

View file

@ -0,0 +1,7 @@
package observers;
public interface ObserverSocketState {
public void updateSocketState(Object o, Object arg);
}

View file

@ -0,0 +1,11 @@
package observers;
import java.util.ArrayList;
import main.Utilisateur;
public interface ObserverUserList {
public void updateList(Object o, ArrayList<Utilisateur> userList);
}

View file

@ -0,0 +1,279 @@
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.InetAddress;
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 InetAddress ipOther;
private TCPClient tcpClient;
private ArrayList<Message> messagesIn;
private ArrayList<Message> messagesOut;
private SQLiteManager sqlManager;
private ArrayList<File> files;
protected ControleurSession(VueSession vue, Socket socketComm, String idOther, String pseudoOther, InetAddress ipOther, 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.ipOther = ipOther;
this.sqlManager = sqlManager;
this.files = new ArrayList<File>();
}
// ---------- ACTION LISTENER OPERATIONS ----------//
@Override
public void actionPerformed(ActionEvent e) {
//Quand le bouton envoyer est presse
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 (MauvaisTypeMessageException | IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
messageOut.setSender("Moi");
this.vue.appendMessage(messageOut);
this.vue.resetZoneSaisie();
this.messagesOut.add(messageOut);
}
}
if((JButton) e.getSource() == this.vue.getButtonImportFile()) {
JFileChooser fc = new JFileChooser();
fc.setMultiSelectionEnabled(true);
int returVal = fc.showDialog(this.vue, "Importer");
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(";");
}
}
}
}
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
@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) {
// TODO Auto-generated method stub
}
protected ArrayList<Message> getHistorique(){
try {
ArrayList<Message> historique = this.sqlManager.getHistoriquesMessages(idOther, pseudoOther);
return historique;
} catch (SQLException e) {
e.printStackTrace();
return new ArrayList<Message>();
}
}
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);
}
}
}
private void askFileTransfer() {
try {
MessageFichier messageOut = new MessageFichier(TypeMessage.FICHIER_INIT, ""+this.files.size(), "");
this.tcpClient.sendMessage(messageOut);
} catch (MauvaisTypeMessageException | IOException e1) {
e1.printStackTrace();
}
}
private void answerFileTransfer(int port) {
try {
MessageFichier messageOut = new MessageFichier(TypeMessage.FICHIER_ANSWER, ""+port, "");
this.tcpClient.sendMessage(messageOut);
} catch (MauvaisTypeMessageException | IOException e1) {
e1.printStackTrace();
}
}
//Methode appelee quand l'inputStream de la socket de communication recoit des donnees
@Override
public void update(Object o, Object arg) {
Message message = (Message) arg;
switch(message.getTypeMessage()) {
case TEXTE:
System.out.println(message.toString());
this.vue.appendMessage(message);
this.messagesIn.add(message);
break;
case IMAGE:
this.vue.appendImage(message);
if(message.getSender().equals("Moi")) {
this.messagesOut.add(message);
}else {
this.messagesIn.add(message);
}
break;
case FICHIER:
this.vue.appendMessage(message);
if(message.getSender().equals("Moi")) {
this.messagesOut.add(message);
}else {
this.messagesIn.add(message);
}
break;
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;
case FICHIER_ANSWER:
try {
MessageFichier mFichier = (MessageFichier) arg;
int port = Integer.parseInt(mFichier.getContenu());
@SuppressWarnings("unchecked")
FileTransferClient ftc = new FileTransferClient(this.ipOther, port ,(ArrayList<File>) this.files.clone(), this);
ftc.sendFiles();
this.files.clear();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
break;
default:
}
}
@Override
public void updateSocketState(Object o, Object arg) {
this.vue.endSession(this.pseudoOther);
}
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.vue = null;
this.tcpClient.destroyAll();
this.tcpClient = null;
}
}

View file

@ -0,0 +1,264 @@
package session;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.InetAddress;
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, InetAddress ipOther, SQLiteManager sqlManager)
throws IOException {
this.c = new ControleurSession(this, socketComm, idOther, pseudoOther, ipOther, sqlManager);
this.setBorder(new EmptyBorder(5, 5, 5, 5));
this.setLayout(new BorderLayout(0, 0));
this.chatInput = new JTextArea();
this.chatInput.setColumns(10);
this.chatInput.setLineWrap(true);
this.chatInput.setWrapStyleWord(true);
this.chatInput.addKeyListener(this.c);
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);
JPanel bottom = new JPanel();
bottom.setLayout(new BorderLayout(0, 0));
// remap "ENTER" to "none" to avoid "\n" in the input area after sending 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");
this.importFile = new JButton("Importer..");
this.importFile.addActionListener(this.c);
JScrollPane inputScroll = new JScrollPane(this.chatInput);
inputScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
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);
this.add(chatScroll, BorderLayout.CENTER);
this.add(bottom, BorderLayout.SOUTH);
this.setPreferredSize(new Dimension(500, 500));
this.displayHistorique();
}
protected JButton getButtonEnvoyer() {
return this.sendMessage;
}
protected JButton getButtonImportFile() {
return this.importFile;
}
protected String getInputedText() {
return this.chatInput.getText();
}
protected void appendInputedText(String str) {
this.chatInput.append(str);
}
protected void resetZoneSaisie() {
this.chatInput.setText("");
}
private void setCaretToEnd() {
this.chatWindow.setCaretPosition(this.chatWindow.getDocument().getLength());
}
protected void appendString(String str) {
try {
Document doc = this.chatWindow.getDocument();
doc.insertString(doc.getLength(), str, null);
} catch (BadLocationException e) {
e.printStackTrace();
}
}
protected void appendMessage(Message message) {
try {
StyledDocument sdoc = this.chatWindow.getStyledDocument();
// sdoc.setParagraphAttributes(sdoc.getLength(), message.toString().length()-1,
// style, false);
sdoc.insertString(sdoc.getLength(), message.toString(), null);
} catch (BadLocationException e) {
e.printStackTrace();
}
}
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");
}
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);
}
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();
}
}
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,318 @@
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, SQLiteManager sqlManager, VueConnexion vueConnexion) throws IOException {
this.vue = vue;
this.vue.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
this.vueConnexion = vueConnexion;
this.tcpServ = new TCPServer();
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) {
InetAddress ipOther = other.getIp();
try {
Socket socketComm = new Socket(ipOther, TCPServer.PORT_SERVER);
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, ipOther, 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) {
e1.printStackTrace();
}
}
}
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());
this.commUDP.sendMessageInfoPseudo();
} 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 update(Object o, Object arg) {
if (o == this.tcpServ) {
Socket sockAccept = (Socket) arg;
try {
String pseudoOther = this.readMessage(sockAccept);
Utilisateur other = this.commUDP.getUserFromPseudo(pseudoOther);
String idOther = other.getId();
InetAddress ipOther = other.getIp();
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,ipOther, 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.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, SQLiteManager sqlManager, VueConnexion vueConnexion) throws IOException {
super(title);
this.tabButtons = new ArrayList<JButton>();
this.sessions = new ArrayList<VueSession>();
this.c = new ControleurStandard(this, commUDP, 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 créer 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);
}
}
};
}