package fr.univ.lyon1.client; import fr.univ.lyon1.common.Channel; import fr.univ.lyon1.common.ChatSSL; import fr.univ.lyon1.common.Message; import fr.univ.lyon1.common.command.Command; import fr.univ.lyon1.common.command.CommandType; import fr.univ.lyon1.common.exception.ChatException; import fr.univ.lyon1.common.exception.NotInChannel; import fr.univ.lyon1.common.exception.UnknownCommand; import javax.net.SocketFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSocket; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; /** * The core of the client side */ public class Client { private final int port; private final String address; private final String username; private final String password; protected final Socket socket; protected final ObjectOutputStream out; private ObjectInputStream in; private final List<Channel> channels = new ArrayList<>(); protected boolean started = false; /** * A client need a server and login * @param address the server address * @param port the server port * @param username thr username * @param password the password * @throws IOException When the initial communication with the server fail */ public Client(String address, int port, String username, String password) throws IOException { this.address = address; this.port = port; this.username = username; this.password = password; socket = initSSL(); out = new ObjectOutputStream(socket.getOutputStream()); getIn(); } /** * Initialise the SSL WebSocket connection with the server * @return the socket * @throws IOException when unable to connect with the server */ private Socket initSSL() throws IOException { SSLContext ctx = ChatSSL.getSSLContext(); SocketFactory factory = ctx.getSocketFactory(); Socket connection = factory.createSocket(address, port); ((SSLSocket) connection).setEnabledProtocols(new String[] {ChatSSL.tlsVersion}); SSLParameters sslParams = new SSLParameters(); sslParams.setEndpointIdentificationAlgorithm("HTTPS"); ((SSLSocket) connection).setSSLParameters(sslParams); return connection; } /** * Close all connection to the server * @throws IOException if fail to close the connection to the server */ public void disconnectedServer() throws IOException { socket.close(); out.close(); if (in != null) in.close(); System.exit(0); } /** * Send a command to the server * @param cmd the command */ private void send(Command cmd) { try { out.writeObject(cmd); out.flush(); } catch (IOException e) { System.err.println("Fail to send command !"); e.printStackTrace(); } } /** * Send a message in the first channel * @param content the content of the message */ public void sendMessage(String content) { send(new Command(CommandType.message, List.of(new Message(content, channels.get(0))))); } /** * Send a command * @param content the command name an args (/commandName arg1 arh2 arg3) * @throws UnknownCommand if the command can't be found */ public void sendCommand(String content) throws UnknownCommand { List<String> args = Arrays.asList(content.split(" ")); String commandName = args.get(0).replace("/", ""); CommandType commandType; try { commandType = CommandType.valueOf(commandName); } catch (IllegalArgumentException e) { throw new UnknownCommand(commandName); } send(new Command(commandType, new ArrayList<>(args.subList(1, args.size())))); } /** * Send a message with a specific channel * @param content the channel name and the message content (#chanemName content of the message) * @throws NotInChannel if the client isn't in the channel */ public void sendMessageChannel(String content) throws NotInChannel { String[] args = content.split(" "); String channelName = args[0].replace("#", ""); content = String.join(" ", Arrays.stream(args).toList().subList(1, args.length)); Channel channel = channels.stream().filter(c -> c.getName().equals(channelName)).findFirst().orElse(null); if (channel == null) throw new NotInChannel(channelName); send(new Command(CommandType.message, List.of(new Message(content, channel)))); } /** * Manage income data from the server * @param data the data * @throws IOException if a connection error occur with the server */ public void action(Object data) throws IOException { if (data instanceof Command) command((Command) data); else if (data instanceof ChatException) ((ChatException) data).printStackTrace(); else { out.writeObject(new UnknownCommand()); out.flush(); } } /** * Command manager * @param cmd the command * @throws IOException if a connection error occur with the server */ private void command(Command cmd) throws IOException { switch (cmd.getType()) { case login -> commandLogin(); case message -> commandMessage(cmd); case list -> commandList(cmd); case listChannels -> commandListChannels(cmd); case join -> commandJoin(cmd); } } /** * After login handler * @throws IOException if a connection error occur with the server */ private void commandLogin() throws IOException { out.writeObject(new Command(CommandType.list, null)); out.flush(); out.writeObject(new Command(CommandType.listChannels, null)); out.flush(); out.writeObject(new Command(CommandType.join, List.of("general"))); out.flush(); } /** * Receiving message from the server * @param cmd the message command */ protected void commandMessage(Command cmd) { System.out.println(); System.out.println(cmd.getArgs().get(0)); } /** * User list handler * @param cmd the command list */ private void commandList(Command cmd) { System.out.println("Users: "+cmd.getArgs().stream().map(Object::toString).collect(Collectors.joining(", "))); } /** * Channel list handler * @param cmd the command channel list */ private void commandListChannels(Command cmd) { System.out.println("Channels: "+cmd.getArgs().stream().map(Object::toString).collect(Collectors.joining(", "))); } /** * The channel join handler * @param cmd the command join */ private void commandJoin(Command cmd) { Channel chan = (Channel) cmd.getArgs().get(0); channels.add(chan); System.out.println("You join "+chan); } /** * Main thread function, creating sub thread for user input and output CLI management * @throws InterruptedException If the client force exit * @throws IOException if a connection error occur with the server */ public void run() throws InterruptedException, IOException { if (started) return; Thread clientSendThread = new Thread(new ClientSend(this, out, socket)); clientSendThread.start(); Thread clientReceiveThread = new Thread(new ClientReceive(this, socket)); clientReceiveThread.start(); started = true; out.writeObject(new Command(CommandType.login, List.of(username, password))); out.flush(); clientSendThread.join(); socket.close(); clientReceiveThread.interrupt(); } /** * Get the in stream of the WebSocket * @return the in stream * @throws IOException if a connection error occur with the server */ public ObjectInputStream getIn() throws IOException { if (in == null) in = new ObjectInputStream(socket.getInputStream()); return in; } }