diff --git a/graphical-app/pom.xml b/graphical-app/pom.xml index 284adfd..c994893 100644 --- a/graphical-app/pom.xml +++ b/graphical-app/pom.xml @@ -47,6 +47,11 @@ validatorfx 0.5.1 + + org.json + json + 20240303 + diff --git a/graphical-app/src/main/java/fr/emiko/graphicalapp/HelloController.java b/graphical-app/src/main/java/fr/emiko/graphicalapp/HelloController.java index c134554..2c99a19 100644 --- a/graphical-app/src/main/java/fr/emiko/graphicalapp/HelloController.java +++ b/graphical-app/src/main/java/fr/emiko/graphicalapp/HelloController.java @@ -1,5 +1,13 @@ package fr.emiko.graphicalapp; +import fr.emiko.graphicsElement.Line; +import fr.emiko.net.DrawClient; +import fr.emiko.net.DrawServer; +import fr.emiko.net.Event; +import fr.emiko.net.User; +import javafx.beans.binding.Bindings; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.fxml.FXMLLoader; @@ -7,26 +15,32 @@ import javafx.fxml.Initializable; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; -import javafx.scene.control.Label; +import javafx.scene.control.*; import javafx.scene.canvas.GraphicsContext; -import javafx.scene.control.MenuItem; -import javafx.scene.control.ScrollPane; -import javafx.scene.control.Slider; +import javafx.scene.effect.BoxBlur; import javafx.scene.input.*; +import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; +import javafx.scene.layout.Priority; import javafx.scene.paint.Color; import fr.emiko.graphicsElement.Stroke; import javafx.scene.robot.Robot; import javafx.scene.transform.Scale; import javafx.stage.Modality; import javafx.stage.Stage; +import org.json.JSONObject; +import java.io.FileNotFoundException; import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; import java.net.URL; -import java.util.ResourceBundle; -import java.util.Vector; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class HelloController implements Initializable { + private final Pattern hostPortPattern = Pattern.compile("^([-.a-zA-Z0-9]+)(?::([0-9]{1,5}))?$"); public Canvas drawingCanvas; public MenuItem saveButton; public MenuItem loadButton; @@ -36,13 +50,30 @@ public class HelloController implements Initializable { public ScrollPane scrollPane; public Label brushSizeLabel; public Pane pane; + public MenuItem hostButton; + public MenuItem joinButton; + public MenuItem disconnectButton; + public SplitPane mainPane; + public MenuItem stopHostButton; + public ColorPicker colorPicker; + public ListView layerListView; + public Button addLayerButton; + public Button removeLayerButton; private double posX = 0; private double posY = 0; private double mouseX = 0; private double mouseY = 0; private Vector strokes = new Vector<>(); - private Vector> lastSaved = new Vector<>(); - private Vector> lines = new Vector<>(); + private Vector lastSaved = new Vector<>(); + private Vector lines = new Vector<>(); + private User user; + private boolean connected; + private DrawClient client; + private ToggleButton hostButtonToggle = new ToggleButton(); + private DrawServer server; + private Canvas currentLayer; + private ObservableList layerObservableList = FXCollections.observableArrayList(); + @Override public void initialize(URL url, ResourceBundle resourceBundle) { @@ -56,6 +87,111 @@ public class HelloController implements Initializable { scrollPane.prefViewportHeightProperty().bind(pane.layoutYProperty()); scrollPane.prefViewportWidthProperty().bind(pane.layoutXProperty()); + stopHostButton.setOnAction(this::onActionStopHost); + hostButton.setOnAction(this::onActionHost); + joinButton.setOnAction(this::onActionJoin); + disconnectButton.setOnAction(this::onActionDisconnect); + + stopHostButton.disableProperty().bind(hostButtonToggle.selectedProperty().not()); + disconnectButton.disableProperty().bind(hostButtonToggle.selectedProperty().not()); + hostButtonToggle.setSelected(false); + mainPane.disableProperty().bind(hostButtonToggle.selectedProperty().not()); + + layerListView.setItems(layerObservableList); + } + + private void onActionStopHost(ActionEvent actionEvent) { + client.close(); + if (this.server != null) { + try { + server.close(); + } catch (IOException e) { + showErrorDialog(e, "Could not close server instance"); + } + } + } + + private void onActionDisconnect(ActionEvent actionEvent) { + client.close(); + } + + + private void onActionJoin(ActionEvent actionEvent) { + TextInputDialog dialog = new TextInputDialog(); + dialog.setTitle("Join"); + dialog.setHeaderText(null); + dialog.setContentText("Enter distant address"); + Optional result = dialog.showAndWait(); + if (result.isPresent()) { + try { + Matcher matcher = hostPortPattern.matcher(result.get()); + matcher.matches(); + String host = matcher.group(1); + String port = matcher.group(2); + connectClient(host, port == null ? 8090 : Integer.parseInt(port)); + } catch (NumberFormatException e) { + showErrorDialog(e, "Invalid distant address"); + } catch (IOException e) { + showErrorDialog(e, "Could not connect to host"); + } + } + } + + private void onActionHost(ActionEvent actionEvent) { + TextInputDialog dialog = new TextInputDialog(); + dialog.setTitle("Host"); + dialog.setContentText("Which port do you want to use? (default: 8090)"); + Optional result = dialog.showAndWait(); + if (result.isPresent()) { + + try { + server = new DrawServer(result.get().isEmpty() ? 8090 : Integer.parseInt(result.get())); + Thread thread = new Thread(server::acceptClients); + thread.setDaemon(true); + thread.start(); + connectClient("localhost", result.get().isEmpty() ? 8090 : Integer.parseInt(result.get())); + } catch (NumberFormatException | IOException e) { + showErrorDialog(e, "Invalid port number"); + } + } + } + + private void connectClient(String host, int port) throws IOException { + this.client = new DrawClient(host, port, this); + hostButtonToggle.setSelected(true); + client.sendAuthEvent(String.valueOf(new Random().nextInt())); + } + + private void showErrorDialog(Exception ex, String context) { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle("An error occured!"); + alert.setHeaderText(null); + alert.setContentText(context); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + ex.printStackTrace(pw); + String exceptionText = sw.toString(); + + Label label = new Label("The exception stacktrace was:"); + + TextArea textArea = new TextArea(exceptionText); + textArea.setEditable(false); + textArea.setWrapText(true); + + textArea.setMaxWidth(Double.MAX_VALUE); + textArea.setMaxHeight(Double.MAX_VALUE); + GridPane.setVgrow(textArea, Priority.ALWAYS); + GridPane.setHgrow(textArea, Priority.ALWAYS); + + GridPane expContent = new GridPane(); + expContent.setMaxWidth(Double.MAX_VALUE); + expContent.add(label, 0, 0); + expContent.add(textArea, 0, 1); + + alert.getDialogPane().setExpandableContent(expContent); + + alert.showAndWait(); } private void setupCanvas() { @@ -73,6 +209,11 @@ public class HelloController implements Initializable { onScrollZoom(event); event.consume(); }}); + BoxBlur blur = new BoxBlur(); + blur.setHeight(1); + blur.setWidth(1); + blur.setIterations(1); + drawingCanvas.getGraphicsContext2D().setEffect(blur); } private void onActionKeyPressed(KeyEvent keyEvent) { @@ -86,7 +227,7 @@ public class HelloController implements Initializable { gc.fillRect(0, 0, drawingCanvas.getWidth(), drawingCanvas.getHeight()); for (Vector strokeVector : lines) { for (Stroke stroke: strokeVector) { - stroke.draw(gc); + stroke.draw(gc, stroke.getColor()); //System.out.println(stroke); } } @@ -164,7 +305,7 @@ public class HelloController implements Initializable { System.out.println(lastSaved.size()); for (Vector strokeVector : lastSaved) { for (Stroke stroke: strokeVector) { - stroke.draw(gc); + stroke.draw(gc, colorPicker.getValue()); System.out.println(stroke); } } @@ -173,7 +314,7 @@ public class HelloController implements Initializable { private void onActionSave(ActionEvent actionEvent) { GraphicsContext gc = drawingCanvas.getGraphicsContext2D(); - lastSaved = (Vector>) lines.clone(); + lastSaved = (Vector) lines.clone(); System.out.println(lastSaved.size()); } @@ -182,10 +323,17 @@ public class HelloController implements Initializable { posY = 0; mouseX = 0; mouseY = 0; - lines.add((Vector) strokes.clone()); + Line line = new Line(); + for (Stroke stroke: strokes) { + line.add(stroke); + } + lines.add((Line) line.clone()); System.out.println(lines.size()); System.out.println(lines); + System.out.println(new Event("ADDLINE", line.toJSONObject())); strokes.clear(); + + client.sendEvent(new Event(Event.ADDLINE, line.toJSONObject())); } private void printLine(MouseEvent mouseEvent) { @@ -197,15 +345,74 @@ public class HelloController implements Initializable { posY = mouseEvent.getY(); } - Stroke stroke = new Stroke(posX, posY, mouseEvent.getX(), mouseEvent.getY(), brushSizeSlider.getValue()); + Stroke stroke = new Stroke(posX, posY, mouseEvent.getX(), mouseEvent.getY(), brushSizeSlider.getValue(), colorPicker.getValue()); strokes.add(stroke); - stroke.draw(gc); + stroke.draw(gc, colorPicker.getValue()); posX = mouseEvent.getX(); posY = mouseEvent.getY(); + + } else if (mouseEvent.isSecondaryButtonDown()) { } } + public void handleEvent(Event event) { + System.out.println("Received new event !:" + event.toJSON()); + String type = event.getType(); + switch (type) { + case Event.LINE -> { + doImportLine(event.getContent()); + } + case Event.DELLINE -> { + doDeleteLine(event.getContent()); + } + default -> {} + } + } + + private void doDeleteLine(JSONObject content) { + lines.remove(Line.fromJSONArray(content.getJSONArray("line"))); + + GraphicsContext gc = drawingCanvas.getGraphicsContext2D(); + lines.sort(new Comparator() { + @Override + public int compare(Line o1, Line o2) { + if (o1.getTimestamp() < o2.getTimestamp()) { + return 1; + } else { + return 0; + } + } + }); + for (Line line: lines) { + for (Stroke stroke: line) { + stroke.draw(gc, colorPicker.getValue()); + } + } + + } + + private void doImportLine(JSONObject content) { + Line importedLine = Line.fromJSONArray(content.getJSONArray("line")); + this.lines.add(importedLine); + GraphicsContext gc = drawingCanvas.getGraphicsContext2D(); + gc.clearRect(0, 0, drawingCanvas.getWidth(), drawingCanvas.getHeight()); + lines.sort(new Comparator() { + @Override + public int compare(Line o1, Line o2) { + if (o1.getTimestamp() < o2.getTimestamp()) { + return 1; + } else { + return 0; + } + } + }); + for (Line line: lines) { + for (Stroke stroke: line) { + stroke.draw(gc, colorPicker.getValue()); + } + } + } } \ No newline at end of file diff --git a/graphical-app/src/main/java/fr/emiko/graphicsElement/Line.java b/graphical-app/src/main/java/fr/emiko/graphicsElement/Line.java new file mode 100644 index 0000000..716780c --- /dev/null +++ b/graphical-app/src/main/java/fr/emiko/graphicsElement/Line.java @@ -0,0 +1,43 @@ +package fr.emiko.graphicsElement; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.Vector; + +public class Line extends Vector { + private int timestamp; + private int layer; + private javafx.scene.paint.Color color; + public JSONObject toJSONObject() { + JSONArray jsonArray = new JSONArray(); + for (Stroke stroke: this) { + jsonArray.put(stroke.toJSON()); + } + return new JSONObject().put("line", jsonArray); + } + + public static Line fromJSONArray(JSONArray jsonArray) { + Line line = new Line(); + for (int i = 0; i < jsonArray.length(); i++) { + line.add(Stroke.fromJSON(jsonArray.getString(i))); + } + return line; + } + + public int getTimestamp() { + return timestamp; + } + + public void setTimestamp(int timestamp) { + this.timestamp = timestamp; + } + + public int getLayer() { + return layer; + } + + public void setLayer(int layer) { + this.layer = layer; + } +} diff --git a/graphical-app/src/main/java/fr/emiko/graphicsElement/Stroke.java b/graphical-app/src/main/java/fr/emiko/graphicsElement/Stroke.java index 55e7e5b..19e4426 100644 --- a/graphical-app/src/main/java/fr/emiko/graphicsElement/Stroke.java +++ b/graphical-app/src/main/java/fr/emiko/graphicsElement/Stroke.java @@ -1,11 +1,14 @@ package fr.emiko.graphicsElement; import javafx.scene.canvas.GraphicsContext; +import javafx.scene.paint.Color; import javafx.scene.shape.Path; import javafx.scene.shape.StrokeLineCap; import javafx.scene.shape.StrokeLineJoin; +import org.json.JSONObject; import java.awt.*; +import java.util.Objects; public class Stroke { private final double fromX; @@ -13,17 +16,31 @@ public class Stroke { private final double toX; private final double toY; private final double brushSize; + private final Color color; - public Stroke (double fromX, double fromY, double toX, double toY, double brushSize) { + public Stroke (double fromX, double fromY, double toX, double toY, double brushSize, Color color) { this.fromX = fromX; this.fromY = fromY; this.toX = toX; this.toY = toY; this.brushSize = brushSize; + this.color = color; } - public void draw (GraphicsContext g) { - g.setStroke(javafx.scene.paint.Color.BLACK); + public static Stroke fromJSON(String jsonStroke) { + JSONObject jsonObject = new JSONObject(jsonStroke); + return new Stroke( + jsonObject.getDouble("fromX"), + jsonObject.getDouble("fromY"), + jsonObject.getDouble("toX"), + jsonObject.getDouble("toY"), + jsonObject.getDouble("brushSize"), + (Color) jsonObject.get("color") + ); + } + + public void draw (GraphicsContext g, javafx.scene.paint.Color color) { + g.setStroke(color); g.setLineCap(StrokeLineCap.ROUND); g.setMiterLimit(1); g.setLineWidth(brushSize); @@ -38,6 +55,34 @@ public class Stroke { @Override public String toString () { - return "Stroke{fromX=%f, fromY=%f, toX=%f, toY=%f, brushSize=%f}".formatted(fromX, fromY, toX, toY, brushSize); +// return "Stroke{fromX=%f, fromY=%f, toX=%f, toY=%f, brushSize=%f}".formatted(fromX, fromY, toX, toY, brushSize); + return this.toJSON(); + } + + public JSONObject toJSONObject() { + return new JSONObject() + .put("fromX", fromX) + .put("fromY", fromY) + .put("toX", toX) + .put("toY", toY) + .put("brushSize", brushSize) + .put("color", color); + } + + public String toJSON() { + return toJSONObject().toString(); + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Stroke stroke = (Stroke) o; + return Double.compare(fromX, stroke.fromX) == 0 && Double.compare(fromY, stroke.fromY) == 0 && Double.compare(toX, stroke.toX) == 0 && Double.compare(toY, stroke.toY) == 0 && Double.compare(brushSize, stroke.brushSize) == 0; + } + + public Color getColor() { + return this.color; } } diff --git a/graphical-app/src/main/java/fr/emiko/net/ClientTCP.java b/graphical-app/src/main/java/fr/emiko/net/ClientTCP.java new file mode 100644 index 0000000..5a5740b --- /dev/null +++ b/graphical-app/src/main/java/fr/emiko/net/ClientTCP.java @@ -0,0 +1,175 @@ +package fr.emiko.net; + +import java.io.*; +import java.net.Socket; +import java.nio.charset.StandardCharsets; + + +/** + * Client TCP : envoie des chaines de caractères à un serveur et lit les chaines en retour. + */ +public class ClientTCP { + /** Couleur rouge */ + public static final String RED = "\u001b[31m"; + /** Couleur bleue */ + public static final String BLUE = "\u001b[34m"; + /** Couleur standard */ + public static final String RST = "\u001b[0m"; + /** Fin de message */ + public static final String END_MESSAGE = "fin"; + + /** + * Socket connecté au serveur + */ + protected Socket sock; + + /** + * Flux de caractères UTF-8 en sortie + */ + protected PrintStream out; + + /** + * Flux de caractères UTF-8 en entrée + */ + protected BufferedReader in; + + /** + * Chaine de caractères "ip:port" du client + */ + protected String ipPort; + + /** + * Le client est-il connecté ? + */ + protected boolean connected; + + + /** + * Programme principal [Déprécié] + * @param args Arguments + * @throws Exception Si la connexion échoue + */ + public static void main(String[] args) throws Exception { + + ClientTCP client = new ClientTCP("localhost", 2024); + Thread envoi = new Thread(client::sendLoop); + Thread reception = new Thread(client::receiveLoop); + envoi.start(); + reception.start(); + envoi.join(); + client.close(); + } + + /** + * Le constructeur ouvre la connexion TCP au serveur host:port + * et récupère les flux de caractères en entrée {@link #in} et sortie {@link #out} + *import static rtgre.chat.ChatApplication.LOGGER; + * @param host IP ou nom de domaine du serveur + * @param port port d'écoute du serveur + * @throws IOException si la connexion échoue ou si les flux ne sont pas récupérables + */ + public ClientTCP(String host, int port) throws IOException { + sock = new Socket(host, port); + ipPort = "%s:%d".formatted(sock.getLocalAddress().getHostAddress(), sock.getLocalPort()); + this.connected = true; + OutputStream os = sock.getOutputStream(); + InputStream is = sock.getInputStream(); + out = new PrintStream(os, true, StandardCharsets.UTF_8); + in = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8), 2048); + Thread rcLoop = new Thread(this::receiveLoop); + rcLoop.setDaemon(true); + rcLoop.start(); + } + + /** + * Envoie une chaine de caractères + * + * @param message chaine de caractères à transmettre + * @throws IOException lorsqu'une erreur sur le flux de sortie est détectée + */ + public void send(String message) throws IOException { + out.println(message); + if (out.checkError()) { + throw new IOException("Output stream error"); + } + } + + /** + * Getter de connected + * @return L'état de connected + */ + public boolean isConnected() { + return connected; + } + + /** + * Setter de connected + * @param connected L'utilisateur est-il connecté ? + */ + public void setConnected(boolean connected) { + this.connected = connected; + } + + /** + * Attente d'une chaine de caractères en entrée. + * + * @return chaine de caractères reçue + * @throws IOException lorsque la fin du flux est atteinte + */ + public String receive() throws IOException { + String message = in.readLine(); + if (message == null) { + throw new IOException("End of the stream has been reached"); + } + return message; + } + + /** + * Fermeture de la connexion TCP + */ + public void close() { + try { + sock.close(); + this.connected = false; + } catch (IOException e) { + } + } + + + /** + * Boucle d'envoi de messages + */ + public void sendLoop() { + BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in)); + connected = true; + try { + while (connected) { + String message = stdIn.readLine(); + if (message == null) { // fin du flux stdIn + message = END_MESSAGE; + } + this.send(message); + if (END_MESSAGE.equals(message)) { + connected = false; + } + } + } catch (IOException e) { + connected = false; + } + } + + /** + * Boucle de réception de messages + */ + public void receiveLoop() { + connected = true; + try { + while (connected) { + String message = this.receive(); + System.out.println("Message received: " + message); + } + } catch (IOException e) { + connected = false; + } + } +} \ No newline at end of file diff --git a/graphical-app/src/main/java/fr/emiko/net/DrawClient.java b/graphical-app/src/main/java/fr/emiko/net/DrawClient.java new file mode 100644 index 0000000..997f701 --- /dev/null +++ b/graphical-app/src/main/java/fr/emiko/net/DrawClient.java @@ -0,0 +1,68 @@ +package fr.emiko.net; + +import fr.emiko.graphicalapp.HelloController; +import javafx.application.Platform; +import org.json.JSONObject; + +import java.io.IOException; + +public class DrawClient extends ClientTCP{ + private final HelloController listener; + + public DrawClient(String host, int port, HelloController listener) throws IOException { + super(host, port); + this.listener = listener; + } + + + + /** + * Envoi d'un évènement, sérialisé dans sa représentation JSON, au serveur. + * @param event L'évènement à envoyer + */ + public void sendEvent(Event event) { + connected = true; + try { + String message = event.toJSON(); + if (message == null) { // fin du flux stdIn + message = END_MESSAGE; + } + this.send(message); + if (END_MESSAGE.equals(message)) { + connected = false; + } + } catch (IOException e) { + connected = false; + } + } + + + /** + * Boucle de réception des messages : chaque message est un évènement sérialisé en JSON, qui est transféré à ChatController.handleEvent(rtgre.modeles.Event) pour traitement. + * Si le message n'est pas conforme (format JSON), la connection est stoppée. + */ + @Override + public void receiveLoop() { + try { + while (connected) { + String message = this.receive(); + if (listener != null) { + Platform.runLater(() -> listener.handleEvent(Event.fromJSON(message))); + } + } + } catch (IOException e) { + connected = false; + } finally { + close(); + } + } + + + public void sendAuthEvent(String login) { + try { + this.send(new Event(Event.AUTH, new JSONObject().put("username", login)).toJSON()); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/graphical-app/src/main/java/fr/emiko/net/DrawServer.java b/graphical-app/src/main/java/fr/emiko/net/DrawServer.java new file mode 100644 index 0000000..bdafaad --- /dev/null +++ b/graphical-app/src/main/java/fr/emiko/net/DrawServer.java @@ -0,0 +1,234 @@ +package fr.emiko.net; + +import java.awt.*; +import java.io.*; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Vector; + +import fr.emiko.graphicsElement.Line; +import org.json.JSONException; +import org.json.JSONObject; + +public class DrawServer { + + private ServerSocket passiveSocket; + private Vector clientList = new Vector(); + private Vector lines; + public DrawServer(int port) throws IOException { + passiveSocket = new ServerSocket(port); + } + + + public static void main(String[] args) throws IOException { + DrawServer server = new DrawServer(8090); + server.acceptClients(); + } + + public void acceptClients() { + while (true) { + try { + Socket sock = passiveSocket.accept(); + handleNewClient(sock); + } catch (IOException e) { + System.out.println(e); + } + } + + } + + + public void removeClient(DrawClientHandler client) { + clientList.remove(client); + } + + + private void handleNewClient(Socket sock) throws IOException { + DrawClientHandler client = new DrawClientHandler(sock); + clientList.add(client); + + Thread clientLoop = new Thread(client::eventReceiveLoop); + clientLoop.start(); + } + + + /** + * Ferme la connexion du serveur, en fermant la connexion auprès de tous ses clients, puis en fermant son socket en écoute passive. + * @throws IOException si la connexion + */ + public void close() throws IOException { + for (DrawClientHandler client : clientList) { + client.close(); + } + passiveSocket.close(); + } + + + + private class DrawClientHandler { + + /** Message de fin d'une connexion */ + public static final String END_MESSAGE = "fin"; + /** + * Socket connecté au client + */ + private Socket sock; + /** + * Flux de caractères en sortie + */ + private PrintStream out; + /** + * Flux de caractères en entrée + */ + private BufferedReader in; + /** + * Chaine de caractères "ip:port" du client + */ + private String ipPort; + private User user; + + /** + * Initialise les attributs {@link #sock} (socket connecté au client), + * {@link #out} (flux de caractères UTF-8 en sortie) et + * {@link #in} (flux de caractères UTF-8 en entrée). + * + * @param sock socket connecté au client + * @throws IOException si la connexion ne peut être établie ou si les flux ne peuvent être récupérés + */ + public DrawClientHandler(Socket sock) throws IOException { + this.sock = sock; + this.ipPort = "%s:%d".formatted(sock.getInetAddress().getHostAddress(), sock.getPort()); + OutputStream os = sock.getOutputStream(); + InputStream is = sock.getInputStream(); + this.out = new PrintStream(os, true, StandardCharsets.UTF_8); + this.in = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); + } + + + /** + * Boucle de réception d'évènement : réceptionne les messages reçus et les délèguent à `handleEvent(java.lang.String)` pour les interpréter + */ + public void eventReceiveLoop() { + try { + String message = null; + while (!END_MESSAGE.equals(message)) { + message = in.readLine(); + if (message == null) { + break; + } + System.out.println("Réception de message : " + message); + try { + if (!handleEvent(message)) { + break; + } + } catch (Exception e) { + System.out.println(e.getMessage()); + break; + } + } + } catch (IOException e) { + System.out.println(e.getMessage()); + } + close(); + } + + + + /** + * Traitement d'un évènement. Ventile vers les méthodes traitant chaque type d'évènement. + * @param message objet évènement sous la forme d'une chaine JSON brute de réception + * @return `false` si l'évènement est de type Event.QUIT , `true` pour tous les autres types. + * @throws JSONException si l'objet JSON n'est pas conforme + * @throws IllegalStateException si l'authentification n'est pas effectuée + */ + private boolean handleEvent(String message) throws JSONException, IllegalStateException { + Event event = Event.fromJSON(message); + switch (event.getType()) { + case Event.AUTH -> { + doLogin(event.getContent()); + return true; + } + case Event.ADDLINE -> { + doAddLine(event.getContent()); + return true; + } + case Event.DELLINE -> { + doDelLine(event.getContent()); + return true; + } + case Event.LSTLINE -> { + doSendLines(); + return true; + } + default -> { + return false; + } + } + } + + private void doDelLine(JSONObject content) { + Line line = Line.fromJSONArray(content.getJSONArray("line")); + this.user.getLines().remove(line); + + sendAllOtherUsers(new Event("DELLINE", line.toJSONObject())); + } + + private void doAddLine(JSONObject content) { + try { + System.out.println(Line.fromJSONArray(content.getJSONArray("line"))); + } catch (Exception e) { + System.out.println(e); + e.printStackTrace(); + } + Line line = Line.fromJSONArray(content.getJSONArray("line")); + this.user.getLines().add(line); + sendAllOtherUsers(new Event("LINE", line.toJSONObject())); + + } + + private void sendAllOtherUsers(Event event) { + for (DrawClientHandler client : clientList) { + if (client.user != this.user) { + System.out.println(client.user.getUsername()); + sendEvent(client, event); + } + } + } + + private void sendEvent(DrawClientHandler client, Event event) { + String jsonEvent = event.toJSON(); + client.out.println(jsonEvent); + } + + private void doSendLines() { + Vector lines = new Vector<>(); + for (DrawClientHandler client: clientList) { + for (Line line: client.user.getLines()) { + lines.add(line); + } + } + for (Line line: lines) { + out.println(new Event("LINELST", line.toJSONObject())); + } + } + + private void doLogin(JSONObject content) { + this.user = new User(content.getString("username")); + } + + + public void close() { + try { + sock.close(); + removeClient(this); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + + } +} diff --git a/graphical-app/src/main/java/fr/emiko/net/Event.java b/graphical-app/src/main/java/fr/emiko/net/Event.java new file mode 100644 index 0000000..73d7cd1 --- /dev/null +++ b/graphical-app/src/main/java/fr/emiko/net/Event.java @@ -0,0 +1,50 @@ +package fr.emiko.net; + +import org.json.JSONObject; + +public class Event { + public static final String AUTH = "AUTH"; + public static final String LSTLINE = "LSTLINE"; + public static final String ADDLINE = "ADDLINE"; + public static final String DELLINE = "DELLINE"; + public static final String LINE = "LINE"; + public static final String LINELST = "LINELST"; + + private String type; + private JSONObject content; + + public Event(String type, JSONObject content) { + this.type = type; + this.content = content; + } + + public String getType() { + return type; + } + + public JSONObject getContent() { + return content; + } + + public JSONObject toJSONObject() { + return new JSONObject() + .put("type", type) + .put("content", content); + } + + public String toJSON() { + return toJSONObject().toString(); + } + + public static Event fromJSON(String obj) { + JSONObject jobj = new JSONObject(obj); + String type = jobj.getString("type"); + JSONObject content = jobj.getJSONObject("content"); + return new Event(type, content); + } + + @Override + public String toString() { + return this.toJSON(); + } +} diff --git a/graphical-app/src/main/java/fr/emiko/net/User.java b/graphical-app/src/main/java/fr/emiko/net/User.java new file mode 100644 index 0000000..edf8f50 --- /dev/null +++ b/graphical-app/src/main/java/fr/emiko/net/User.java @@ -0,0 +1,32 @@ +package fr.emiko.net; + +import fr.emiko.graphicsElement.Line; + +import java.util.Vector; + +public class User { + private String username; + private String hashedPassword = ""; + private Vector lines = new Vector(); + private boolean connected; + + public User(String username) { + this.username = username; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public Vector getLines() { + return lines; + } + + public void setLines(Vector lines) { + this.lines = lines; + } +} diff --git a/graphical-app/src/main/java/module-info.java b/graphical-app/src/main/java/module-info.java index 60398df..682ea3e 100644 --- a/graphical-app/src/main/java/module-info.java +++ b/graphical-app/src/main/java/module-info.java @@ -4,6 +4,7 @@ module fr.emiko.graphicalapp { requires java.desktop; requires org.controlsfx.controls; requires net.synedra.validatorfx; + requires org.json; opens fr.emiko.graphicalapp to javafx.fxml; diff --git a/graphical-app/src/main/resources/fr/emiko/graphicalapp/hello-view.fxml b/graphical-app/src/main/resources/fr/emiko/graphicalapp/hello-view.fxml index 575f6e2..166c06e 100644 --- a/graphical-app/src/main/resources/fr/emiko/graphicalapp/hello-view.fxml +++ b/graphical-app/src/main/resources/fr/emiko/graphicalapp/hello-view.fxml @@ -2,15 +2,20 @@ + + + + + @@ -18,28 +23,36 @@ - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + - - - + + + @@ -71,10 +84,27 @@ + + + + + + + + + + + +