Feat: add offline mode to prevent errors when trying to send requests to not connected server
This commit is contained in:
@@ -26,6 +26,7 @@ public class ClientService {
|
|||||||
private final Socket socket;
|
private final Socket socket;
|
||||||
|
|
||||||
private final ExecutorService executor;
|
private final ExecutorService executor;
|
||||||
|
private final boolean offlineMode;
|
||||||
|
|
||||||
public static ArrayList<String> response;
|
public static ArrayList<String> response;
|
||||||
private final AtomicInteger idGenerator;
|
private final AtomicInteger idGenerator;
|
||||||
@@ -42,6 +43,7 @@ public class ClientService {
|
|||||||
|
|
||||||
this.idGenerator = new AtomicInteger(0);
|
this.idGenerator = new AtomicInteger(0);
|
||||||
|
|
||||||
|
this.offlineMode = false;
|
||||||
try {
|
try {
|
||||||
socket = new Socket(ip, port);
|
socket = new Socket(ip, port);
|
||||||
clienttcptransport = new TcpTransport(socket);
|
clienttcptransport = new TcpTransport(socket);
|
||||||
@@ -52,6 +54,27 @@ public class ClientService {
|
|||||||
executor = Executors.newSingleThreadExecutor();
|
executor = Executors.newSingleThreadExecutor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a ClientService in offline mode. No network connection will be
|
||||||
|
* attempted and calls to processCommand will throw a RuntimeException.
|
||||||
|
*
|
||||||
|
* @param offline true to create an offline (no-network) client service
|
||||||
|
*/
|
||||||
|
public ClientService(boolean offline) {
|
||||||
|
this.idGenerator = new AtomicInteger(0);
|
||||||
|
this.offlineMode = offline;
|
||||||
|
this.socket = null;
|
||||||
|
this.clienttcptransport = null;
|
||||||
|
this.executor = Executors.newSingleThreadExecutor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this ClientService is running in offline mode (no network).
|
||||||
|
*/
|
||||||
|
public boolean isOffline() {
|
||||||
|
return offlineMode;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a command to the server and waits for the response. The command is
|
* Sends a command to the server and waits for the response. The command is
|
||||||
* sent using the TcpTransport, and the response is read in a loop until a
|
* sent using the TcpTransport, and the response is read in a loop until a
|
||||||
@@ -62,6 +85,9 @@ public class ClientService {
|
|||||||
* @return The response from the server as a string.
|
* @return The response from the server as a string.
|
||||||
*/
|
*/
|
||||||
protected String processCommand(String message) {
|
protected String processCommand(String message) {
|
||||||
|
if (offlineMode) {
|
||||||
|
throw new RuntimeException("ClientService is offline: cannot process command");
|
||||||
|
}
|
||||||
AtomicReference<String> response = new AtomicReference<>();
|
AtomicReference<String> response = new AtomicReference<>();
|
||||||
sendRequest(() -> {
|
sendRequest(() -> {
|
||||||
try {
|
try {
|
||||||
@@ -135,8 +161,12 @@ public class ClientService {
|
|||||||
public void closeSocket() {
|
public void closeSocket() {
|
||||||
try {
|
try {
|
||||||
executor.shutdown();
|
executor.shutdown();
|
||||||
clienttcptransport.close();
|
if (clienttcptransport != null) {
|
||||||
socket.close();
|
clienttcptransport.close();
|
||||||
|
}
|
||||||
|
if (socket != null) {
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
} catch (IOException j) {
|
} catch (IOException j) {
|
||||||
System.out.println(j);
|
System.out.println(j);
|
||||||
}
|
}
|
||||||
|
|||||||
+84
-8
@@ -3,6 +3,9 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui;
|
|||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.control.Alert.AlertType;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
@@ -12,6 +15,7 @@ import javafx.scene.shape.Rectangle;
|
|||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.client.network.LobbyClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller for the Casono main UI lobby. Handles UI initialization and user
|
* Controller for the Casono main UI lobby. Handles UI initialization and user
|
||||||
@@ -20,17 +24,29 @@ import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
|||||||
public class CasinomainuiController {
|
public class CasinomainuiController {
|
||||||
private static final Logger LOGGER = LogManager.getLogger(CasinomainuiController.class);
|
private static final Logger LOGGER = LogManager.getLogger(CasinomainuiController.class);
|
||||||
|
|
||||||
@FXML private AnchorPane rootPane;
|
@FXML
|
||||||
@FXML private Label titleLabel;
|
private AnchorPane rootPane;
|
||||||
@FXML private Label subtitleLabel;
|
@FXML
|
||||||
@FXML private ImageView logoView;
|
private Label titleLabel;
|
||||||
@FXML private Rectangle greenBox;
|
@FXML
|
||||||
@FXML private Button exitbutton;
|
private Label subtitleLabel;
|
||||||
@FXML private VBox casinoTable;
|
@FXML
|
||||||
|
private ImageView logoView;
|
||||||
|
@FXML
|
||||||
|
private Rectangle greenBox;
|
||||||
|
@FXML
|
||||||
|
private Button exitbutton;
|
||||||
|
@FXML
|
||||||
|
private VBox casinoTable;
|
||||||
|
@FXML
|
||||||
|
private TextField usernameField;
|
||||||
|
@FXML
|
||||||
|
private Button loginButton;
|
||||||
|
|
||||||
private LobbyButtonTranslationManager translationManager;
|
private LobbyButtonTranslationManager translationManager;
|
||||||
private LobbyButtonGridManager gridManager;
|
private LobbyButtonGridManager gridManager;
|
||||||
private int nextButtonId = 1;
|
private int nextButtonId = 1;
|
||||||
|
private LobbyClient lobbyClient;
|
||||||
|
|
||||||
/** Default constructor for dependency injection by FXMLLoader. */
|
/** Default constructor for dependency injection by FXMLLoader. */
|
||||||
public CasinomainuiController() {
|
public CasinomainuiController() {
|
||||||
@@ -47,8 +63,60 @@ public class CasinomainuiController {
|
|||||||
translationManager = LobbyButtonTranslationManager.getInstance();
|
translationManager = LobbyButtonTranslationManager.getInstance();
|
||||||
String host = System.getProperty("casono.server.host");
|
String host = System.getProperty("casono.server.host");
|
||||||
int port = Integer.parseInt(System.getProperty("casono.server.port"));
|
int port = Integer.parseInt(System.getProperty("casono.server.port"));
|
||||||
ClientService clientService = new ClientService(host, port);
|
ClientService clientService;
|
||||||
|
try {
|
||||||
|
clientService = new ClientService(host, port);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
LOGGER.warn("Could not connect to server {}:{} — starting in offline mode: {}", host, port, e.getMessage());
|
||||||
|
clientService = new ClientService(true); // offline mode
|
||||||
|
}
|
||||||
gridManager = new LobbyButtonGridManager(new javafx.scene.layout.GridPane(), translationManager, clientService);
|
gridManager = new LobbyButtonGridManager(new javafx.scene.layout.GridPane(), translationManager, clientService);
|
||||||
|
// LobbyClient will use the provided ClientService; in offline mode calls will
|
||||||
|
// fail with RuntimeException
|
||||||
|
lobbyClient = new LobbyClient(clientService);
|
||||||
|
casinoTable.getChildren().clear();
|
||||||
|
casinoTable.getChildren().add(gridManager.getGridPane());
|
||||||
|
gridManager.renderLobbyButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the login button action. Validates input and calls
|
||||||
|
* LobbyClient.login().
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
public void handleLoginButton() {
|
||||||
|
String username = usernameField.getText();
|
||||||
|
if (username == null || username.isBlank()) {
|
||||||
|
showAlert("Please enter a username.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Only allow alphanumeric, _ and -
|
||||||
|
if (!username.matches("[a-zA-Z0-9_-]+")) {
|
||||||
|
showAlert("Only letters, numbers, '_' and '-' are allowed!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (lobbyClient.getClientService().isOffline()) {
|
||||||
|
showAlert("Offline mode: cannot send login to server.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
lobbyClient.login(username);
|
||||||
|
showAlert("Login sent: " + username);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
LOGGER.error("Login failed: {}", e.getMessage());
|
||||||
|
showAlert("Login failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows an alert dialog with the given message.
|
||||||
|
*/
|
||||||
|
private void showAlert(String message) {
|
||||||
|
Alert alert = new Alert(AlertType.INFORMATION);
|
||||||
|
alert.setTitle("Info");
|
||||||
|
alert.setHeaderText(null);
|
||||||
|
alert.setContentText(message);
|
||||||
|
alert.showAndWait();
|
||||||
casinoTable.getChildren().clear();
|
casinoTable.getChildren().clear();
|
||||||
casinoTable.getChildren().add(gridManager.getGridPane());
|
casinoTable.getChildren().add(gridManager.getGridPane());
|
||||||
gridManager.renderLobbyButtons();
|
gridManager.renderLobbyButtons();
|
||||||
@@ -69,6 +137,14 @@ public class CasinomainuiController {
|
|||||||
}
|
}
|
||||||
int buttonId = nextButtonId++;
|
int buttonId = nextButtonId++;
|
||||||
try {
|
try {
|
||||||
|
String username = usernameField != null ? usernameField.getText() : "<unknown>";
|
||||||
|
LOGGER.info("Creating lobby for user: {}", username);
|
||||||
|
// avoid attempting to create a lobby when offline
|
||||||
|
if (lobbyClient.getClientService().isOffline()) {
|
||||||
|
LOGGER.warn("Cannot create lobby while offline");
|
||||||
|
showAlert("Offline mode: cannot create lobby.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
int lobbyId = gridManager.createLobby();
|
int lobbyId = gridManager.createLobby();
|
||||||
translationManager.addLobbyButton(buttonId, lobbyId);
|
translationManager.addLobbyButton(buttonId, lobbyId);
|
||||||
LOGGER.info("ButtonID: {}, LobbyID: {}", buttonId, lobbyId);
|
LOGGER.info("ButtonID: {}, LobbyID: {}", buttonId, lobbyId);
|
||||||
|
|||||||
Reference in New Issue
Block a user