diff --git a/src/fr/univ/lyon1/client/Client.java b/src/fr/univ/lyon1/client/Client.java index b5f33cb..33afdfe 100644 --- a/src/fr/univ/lyon1/client/Client.java +++ b/src/fr/univ/lyon1/client/Client.java @@ -1,11 +1,13 @@ package fr.univ.lyon1.client; import fr.univ.lyon1.common.Message; +import fr.univ.lyon1.common.User; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; +import java.util.List; public class Client { private final int port; @@ -15,12 +17,16 @@ public class Client { private ObjectInputStream in; protected boolean started = false; - public Client(String address, int port, String uuid, String password) throws IOException, InterruptedException, Exception { + public Client(String address, int port, String uuid, String password) throws Exception { this.address = address; this.port = port; socket = new Socket(address, port); out = new ObjectOutputStream(socket.getOutputStream()); while (!this.auth(uuid, password)); + out.writeObject("listUsers"); + out.flush(); + out.writeObject("join general"); + out.flush(); } public boolean auth(String uuid, String password) throws IOException { @@ -52,10 +58,9 @@ public class Client { } public String sendMessage(String content) { - Message msg = new Message(null, content); try { - out.writeObject(msg); + out.writeObject(new Message(content)); out.flush(); } catch (IOException e) { System.err.println("Fail to send message !"); @@ -71,9 +76,24 @@ public class Client { return msg; } + public void action(Object data) { + if (data instanceof Message) + messageReceived((Message) data); + else if (data instanceof List) { + List tmpList = (List) data; + if (tmpList.get(0) instanceof User) { + List users = (List) data; + for (User u : users) { + System.out.println(u); + } + } + } + } + public void run() throws InterruptedException, IOException { if (started) return; + Thread clientSendThread = new Thread(new ClientSend(this, out, socket)); clientSendThread.start(); diff --git a/src/fr/univ/lyon1/client/ClientReceive.java b/src/fr/univ/lyon1/client/ClientReceive.java index 4a906b2..f71a9f1 100644 --- a/src/fr/univ/lyon1/client/ClientReceive.java +++ b/src/fr/univ/lyon1/client/ClientReceive.java @@ -1,7 +1,5 @@ package fr.univ.lyon1.client; -import fr.univ.lyon1.common.Message; - import java.io.IOException; import java.io.ObjectInputStream; import java.net.Socket; @@ -27,15 +25,15 @@ public class ClientReceive implements Runnable { } while(true) { - Message msg; + Object data; try { - msg = (Message) in.readObject(); + data = in.readObject(); } catch (ClassNotFoundException|IOException e) { if (e instanceof SocketException) { System.err.println("Connexion closed"); break; } - System.err.println("Fail to read message object !"); + System.err.println("Fail to read object !"); e.printStackTrace(); try { Thread.sleep(1000); @@ -45,10 +43,10 @@ public class ClientReceive implements Runnable { continue; } - if (msg == null) + if (data == null) break; - this.client.messageReceived(msg); + this.client.action(data); } try { diff --git a/src/fr/univ/lyon1/common/Channel.java b/src/fr/univ/lyon1/common/Channel.java new file mode 100644 index 0000000..1ef983d --- /dev/null +++ b/src/fr/univ/lyon1/common/Channel.java @@ -0,0 +1,32 @@ +package fr.univ.lyon1.common; + +import java.io.Serializable; +import java.util.UUID; + +public class Channel implements Serializable { + private final UUID uuid; + private String name; + + public Channel(UUID uuid, String name) { + this.uuid = uuid; + this.name = name; + } + + public Channel(String name) { + this.uuid = UUID.randomUUID(); + this.name = name; + } + + public UUID getUUID() { + return uuid; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return name; + } +} diff --git a/src/fr/univ/lyon1/common/Message.java b/src/fr/univ/lyon1/common/Message.java index c8e5b8f..f179891 100644 --- a/src/fr/univ/lyon1/common/Message.java +++ b/src/fr/univ/lyon1/common/Message.java @@ -4,27 +4,43 @@ import java.io.Serializable; import java.util.UUID; public class Message implements Serializable { + private Channel channel; private User sender; private final String content; private final UUID uuid; - public Message(User sender, String content) { + public Message(Channel channel, User sender, String content) { this.uuid = UUID.randomUUID(); + this.channel = channel; this.sender = sender; this.content = content; } - public Message(UUID uuid, User sender, String content) { + public Message(UUID uuid, Channel channel, User sender, String content) { this.uuid = uuid; + this.channel = channel; this.sender = sender; this.content = content; } + public Message(String content) { + this.uuid = UUID.randomUUID(); + this.content = content; + } + + public Message repley(User user, String content) { + return new Message(this.channel, user, content); + } + public void setSender(User sender) { this.sender = sender; } + public Channel getChannel() { + return channel; + } + public User getSender() { return sender; } @@ -35,6 +51,9 @@ public class Message implements Serializable { @Override public String toString() { - return sender + ": " + content; + if (channel != null) + return "#"+channel+" "+sender+": "+content; + else + return sender + ": " + content; } } diff --git a/src/fr/univ/lyon1/common/User.java b/src/fr/univ/lyon1/common/User.java index 5a14351..dd0f0c8 100644 --- a/src/fr/univ/lyon1/common/User.java +++ b/src/fr/univ/lyon1/common/User.java @@ -1,16 +1,7 @@ package fr.univ.lyon1.common; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.PBEKeySpec; import java.io.Serializable; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.KeySpec; -import java.util.Arrays; -import java.util.Base64; import java.util.UUID; -import java.util.regex.Matcher; public class User implements Serializable { private final UUID uuid; diff --git a/src/fr/univ/lyon1/common/channel/Channel.java b/src/fr/univ/lyon1/common/channel/Channel.java deleted file mode 100644 index fce3126..0000000 --- a/src/fr/univ/lyon1/common/channel/Channel.java +++ /dev/null @@ -1,13 +0,0 @@ -package fr.univ.lyon1.common.channel; - -import java.util.UUID; - -public abstract class Channel { - private final UUID uuid; - private String name; - - public Channel(UUID uuid, String name) { - this.uuid = uuid; - this.name = name; - } -} diff --git a/src/fr/univ/lyon1/common/channel/PrivateChannel.java b/src/fr/univ/lyon1/common/channel/PrivateChannel.java deleted file mode 100644 index 92ecbd8..0000000 --- a/src/fr/univ/lyon1/common/channel/PrivateChannel.java +++ /dev/null @@ -1,10 +0,0 @@ -package fr.univ.lyon1.common.channel; - -import java.util.UUID; - -public class PrivateChannel extends Channel { - - public PrivateChannel(UUID uuid, String name) { - super(uuid, name); - } -} diff --git a/src/fr/univ/lyon1/common/channel/PublicChannel.java b/src/fr/univ/lyon1/common/channel/PublicChannel.java deleted file mode 100644 index 068dec2..0000000 --- a/src/fr/univ/lyon1/common/channel/PublicChannel.java +++ /dev/null @@ -1,9 +0,0 @@ -package fr.univ.lyon1.common.channel; - -import java.util.UUID; - -public class PublicChannel extends Channel { - public PublicChannel(UUID uuid, String name) { - super(uuid, name); - } -} diff --git a/src/fr/univ/lyon1/server/ConnectedClient.java b/src/fr/univ/lyon1/server/ConnectedClient.java index 8f3de7c..3f583d7 100644 --- a/src/fr/univ/lyon1/server/ConnectedClient.java +++ b/src/fr/univ/lyon1/server/ConnectedClient.java @@ -1,7 +1,9 @@ package fr.univ.lyon1.server; +import fr.univ.lyon1.common.Channel; import fr.univ.lyon1.common.Message; import fr.univ.lyon1.common.User; +import fr.univ.lyon1.server.models.ChannelModel; import fr.univ.lyon1.server.models.UserModel; import java.io.EOFException; @@ -9,6 +11,7 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; +import java.util.List; public class ConnectedClient implements Runnable { private static int idCounter = 0; @@ -17,7 +20,7 @@ public class ConnectedClient implements Runnable { private final Socket socket; private final ObjectOutputStream out; private ObjectInputStream in; - private User user; + private UserModel user; ConnectedClient(Server server, Socket socket) throws IOException { this.server = server; @@ -66,16 +69,66 @@ public class ConnectedClient implements Runnable { return message; } + private void actionMessage(Object data) throws IOException, ClassNotFoundException { + Message msg = (Message) data; + + if (msg.getContent().startsWith("/")) + command(msg); + else { + msg.setSender(this.user); + server.broadcastMessage(msg, id); + } + } + + private void command(Message msg) throws IOException { + String content = msg.getContent(); + if (content.equals("/list")) { + List users = server.getUsers(); + sendMessage(msg.repley(Server.getServerUser(), "Total: "+users.toArray().length + "\n" + String.join(", ", users.stream().map(User::getUsername).toList()))); + } else if (content.startsWith("/join")) + actionJoinChannel(content); + } + + private void actionListUsers() throws IOException { + out.writeObject(server.getUsers()); + out.flush(); + } + + private void actionJoinChannel(String msg) throws IOException { + String name = msg.replaceAll("^(/?join) ", ""); + System.out.println(name); + ChannelModel chan = ChannelModel.get(name); + + if (chan == null) { + chan = new ChannelModel(name); + chan.addUser(user); + } else + if (!chan.have(user)) + chan.addUser(user); + + out.writeObject(chan); + out.flush(); + + server.broadcastMessage(new Message(chan, Server.getServerUser(), user.getUsername()+" joined the channel !"), -1); + } + public void run() { try { while (true) { - Message msg = (Message) in.readObject(); - if (msg == null) + Object data = in.readObject(); + if (data == null) break; - msg.setSender(this.user); - server.broadcastMessage(msg, id); + if (data instanceof Message) + actionMessage(data); + else if (data instanceof String) { + String msg = (String) data; + if (data.equals("listUsers")) + actionListUsers(); + else if (((String) data).startsWith("join")) + actionJoinChannel(msg); + } } } catch (IOException | ClassNotFoundException e) { if (!(e instanceof EOFException)) { @@ -97,4 +150,8 @@ public class ConnectedClient implements Runnable { public int getId() { return id; } + + public User getUser() { + return user; + } } diff --git a/src/fr/univ/lyon1/server/Server.java b/src/fr/univ/lyon1/server/Server.java index 3e7d64f..9ab29d6 100644 --- a/src/fr/univ/lyon1/server/Server.java +++ b/src/fr/univ/lyon1/server/Server.java @@ -2,6 +2,7 @@ package fr.univ.lyon1.server; import fr.univ.lyon1.common.Message; import fr.univ.lyon1.common.User; +import fr.univ.lyon1.server.models.UserChannelModel; import java.io.IOException; import java.util.ArrayList; @@ -21,19 +22,16 @@ public class Server { } public ConnectedClient addClient(ConnectedClient newClient) { - Message msg = new Message( serverUser, newClient.getId() + " is connected !"); - clients.add(newClient); - broadcastMessage(msg, -1); - - System.out.println("Client "+newClient.getId()+" is connected !"); + System.out.println("Client "+newClient.getUser().getUsername()+" is connected !"); return newClient; } public int broadcastMessage(Message message, int id) { - for (ConnectedClient client : clients) { + List users = UserChannelModel.getUsers(message.getChannel()).stream().map(User::getUUID).toList(); + for (ConnectedClient client : clients.stream().filter(connectedClient -> users.contains(connectedClient.getUser().getUUID())).toList()) { if (id == -1 || client.getId() != id) try { client.sendMessage(message); @@ -55,10 +53,6 @@ public class Server { clients.remove(client); - Message msg = new Message(serverUser, "Client "+client.getId()+" is disconnected"); - - broadcastMessage(msg, -1); - System.out.println("Client "+client.getId()+" disconnected"); return client; } @@ -66,4 +60,12 @@ public class Server { public int getPort() { return port; } + + public static User getServerUser() { + return serverUser; + } + + public List getUsers() { + return clients.stream().map(ConnectedClient::getUser).toList(); + } } diff --git a/src/fr/univ/lyon1/server/models/ChannelModel.java b/src/fr/univ/lyon1/server/models/ChannelModel.java new file mode 100644 index 0000000..3c744dc --- /dev/null +++ b/src/fr/univ/lyon1/server/models/ChannelModel.java @@ -0,0 +1,112 @@ +package fr.univ.lyon1.server.models; + +import fr.univ.lyon1.common.Channel; +import fr.univ.lyon1.common.User; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.UUID; + +public class ChannelModel extends Channel implements Model { + private static final String TABLE_NAME = "Channel"; + + public ChannelModel(String name) { + super(name); + create(); + } + + private ChannelModel(UUID uuid, String name) { + super(uuid, name); + } + + public void addUser(User user) { + new UserChannelModel(user, this); + } + + public boolean have(User user) { + return UserChannelModel.exist(user, this); + } + + public static ChannelModel get(String name) { + try { + PreparedStatement ps = database.getConnection().prepareStatement("SELECT UUID FROM "+TABLE_NAME+" WHERE name = ?"); + ps.setString(1, name); + if (ps.execute()) { + ResultSet rs = ps.getResultSet(); + if (rs.next()) + return get(UUID.fromString(rs.getString("UUID"))); + } + } catch (SQLException err) { + err.printStackTrace(); + return null; + } + return null; + } + + public static ChannelModel get(UUID uuid) { + try { + PreparedStatement ps = database.getConnection().prepareStatement("SELECT * FROM "+TABLE_NAME+" WHERE UUID = ?"); + ps.setString(1, uuid.toString()); + if (ps.execute()) { + ResultSet rs = ps.getResultSet(); + if (rs.next()) + return new ChannelModel( + UUID.fromString(rs.getString("UUID")), + rs.getString("NAME") + ); + } + } catch (SQLException err) { + err.printStackTrace(); + return null; + } + return null; + } + + private boolean exist() { + try { + PreparedStatement ps = database.getConnection().prepareStatement("SELECT UUID FROM "+TABLE_NAME+" WHERE UUID = ?"); + ps.setString(1, super.getUUID().toString()); + ps.execute(); + return ps.getResultSet().next(); + } catch (SQLException err) { + err.printStackTrace(); + return false; + } + } + + private boolean create() { + try { + PreparedStatement ps = database.getConnection().prepareStatement("INSERT INTO "+TABLE_NAME+" (UUID, name) VALUES (?, ?)"); + ps.setString(1, super.getUUID().toString()); + ps.setString(2, super.getName()); + return ps.executeUpdate() > 0; + } catch (SQLException err) { + err.printStackTrace(); + return false; + } + } + + public boolean save() { + if (!exist()) + return create(); + + try { + PreparedStatement ps = database.getConnection().prepareStatement("UPDATE "+TABLE_NAME+" SET name = ? WHERE UUID = ?"); + ps.setString(1, super.getName()); + return ps.executeUpdate() > 0; + } catch (SQLException err) { + err.printStackTrace(); + return false; + } + } + + public static void generateTable() { + try { + PreparedStatement ps = database.getConnection().prepareStatement("CREATE TABLE "+TABLE_NAME+" ( UUID varchar(40) primary key, name varchar(16) unique )"); + ps.executeUpdate(); + } catch (SQLException err) { + err.printStackTrace(); + } + } +} diff --git a/src/fr/univ/lyon1/server/models/Model.java b/src/fr/univ/lyon1/server/models/Model.java index aca3034..5903bdb 100644 --- a/src/fr/univ/lyon1/server/models/Model.java +++ b/src/fr/univ/lyon1/server/models/Model.java @@ -5,6 +5,4 @@ import fr.univ.lyon1.server.Database; public interface Model { Database database = Database.getDatabase(); - - } diff --git a/src/fr/univ/lyon1/server/models/UserChannelModel.java b/src/fr/univ/lyon1/server/models/UserChannelModel.java new file mode 100644 index 0000000..3858d62 --- /dev/null +++ b/src/fr/univ/lyon1/server/models/UserChannelModel.java @@ -0,0 +1,96 @@ +package fr.univ.lyon1.server.models; + +import fr.univ.lyon1.common.Channel; +import fr.univ.lyon1.common.User; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class UserChannelModel implements Model { + private User user; + private Channel channel; + + private static final String TABLE_NAME = "UserChannel"; + + public UserChannelModel(User user, Channel channel) { + this.user = user; + this.channel = channel; + + if (!exist(user, channel)) + create(); + } + + public static List getUsers(Channel channel) { + List users = new ArrayList<>(); + + try { + PreparedStatement ps = database.getConnection().prepareStatement("SELECT userUUID FROM "+TABLE_NAME+" WHERE channelUUID = ?"); + ps.setString(1, channel.getUUID().toString()); + if (ps.execute()) { + ResultSet rs = ps.getResultSet(); + while (rs.next()) + users.add(UserModel.get(UUID.fromString(rs.getString("userUUID")))); + } + } catch (SQLException err) { + err.printStackTrace(); + return null; + } + return users; + } + + public static List getChannels(User user) { + List channels = new ArrayList<>(); + + try { + PreparedStatement ps = database.getConnection().prepareStatement("SELECT channelUUID FROM "+TABLE_NAME+" WHERE userUUID = ?"); + ps.setString(1, user.getUUID().toString()); + if (ps.execute()) { + ResultSet rs = ps.getResultSet(); + while (rs.next()) + channels.add(ChannelModel.get(UUID.fromString(rs.getString("channelUUID")))); + } + } catch (SQLException err) { + err.printStackTrace(); + return null; + } + return channels; + } + + public static boolean exist(User user, Channel channel) { + try { + PreparedStatement ps = database.getConnection().prepareStatement("SELECT 1 FROM "+TABLE_NAME+" WHERE userUUID = ? AND channelUUID = ?"); + ps.setString(1, user.getUUID().toString()); + ps.setString(2, channel.getUUID().toString()); + ps.execute(); + return ps.getResultSet().next(); + } catch (SQLException err) { + err.printStackTrace(); + return false; + } + } + + private boolean create() { + try { + PreparedStatement ps = database.getConnection().prepareStatement("INSERT INTO "+TABLE_NAME+" (userUUID, channelUUID) VALUES (?, ?)"); + ps.setString(1, user.getUUID().toString()); + ps.setString(2, channel.getUUID().toString()); + return ps.executeUpdate() > 0; + } catch (SQLException err) { + err.printStackTrace(); + return false; + } + } + + public static void generateTable() { + try { + PreparedStatement ps = database.getConnection().prepareStatement("CREATE TABLE "+TABLE_NAME+" (userUUID varchar(40) not null references User(UUID), channelUUID varchar(40) not null references Channel(UUID), PRIMARY KEY (userUUID, channelUUID))"); + ps.executeUpdate(); + } catch (SQLException err) { + err.printStackTrace(); + } + } +}