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 ExecutorService executor;
|
||||
private final boolean offlineMode;
|
||||
|
||||
public static ArrayList<String> response;
|
||||
private final AtomicInteger idGenerator;
|
||||
@@ -42,6 +43,7 @@ public class ClientService {
|
||||
|
||||
this.idGenerator = new AtomicInteger(0);
|
||||
|
||||
this.offlineMode = false;
|
||||
try {
|
||||
socket = new Socket(ip, port);
|
||||
clienttcptransport = new TcpTransport(socket);
|
||||
@@ -52,6 +54,27 @@ public class ClientService {
|
||||
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
|
||||
* 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.
|
||||
*/
|
||||
protected String processCommand(String message) {
|
||||
if (offlineMode) {
|
||||
throw new RuntimeException("ClientService is offline: cannot process command");
|
||||
}
|
||||
AtomicReference<String> response = new AtomicReference<>();
|
||||
sendRequest(() -> {
|
||||
try {
|
||||
@@ -135,8 +161,12 @@ public class ClientService {
|
||||
public void closeSocket() {
|
||||
try {
|
||||
executor.shutdown();
|
||||
clienttcptransport.close();
|
||||
socket.close();
|
||||
if (clienttcptransport != null) {
|
||||
clienttcptransport.close();
|
||||
}
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
}
|
||||
} catch (IOException 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.fxml.FXML;
|
||||
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.image.Image;
|
||||
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.Logger;
|
||||
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
|
||||
@@ -20,17 +24,29 @@ import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
||||
public class CasinomainuiController {
|
||||
private static final Logger LOGGER = LogManager.getLogger(CasinomainuiController.class);
|
||||
|
||||
@FXML private AnchorPane rootPane;
|
||||
@FXML private Label titleLabel;
|
||||
@FXML private Label subtitleLabel;
|
||||
@FXML private ImageView logoView;
|
||||
@FXML private Rectangle greenBox;
|
||||
@FXML private Button exitbutton;
|
||||
@FXML private VBox casinoTable;
|
||||
@FXML
|
||||
private AnchorPane rootPane;
|
||||
@FXML
|
||||
private Label titleLabel;
|
||||
@FXML
|
||||
private Label subtitleLabel;
|
||||
@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 LobbyButtonGridManager gridManager;
|
||||
private int nextButtonId = 1;
|
||||
private LobbyClient lobbyClient;
|
||||
|
||||
/** Default constructor for dependency injection by FXMLLoader. */
|
||||
public CasinomainuiController() {
|
||||
@@ -47,8 +63,60 @@ public class CasinomainuiController {
|
||||
translationManager = LobbyButtonTranslationManager.getInstance();
|
||||
String host = System.getProperty("casono.server.host");
|
||||
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);
|
||||
// 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().add(gridManager.getGridPane());
|
||||
gridManager.renderLobbyButtons();
|
||||
@@ -69,6 +137,14 @@ public class CasinomainuiController {
|
||||
}
|
||||
int buttonId = nextButtonId++;
|
||||
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();
|
||||
translationManager.addLobbyButton(buttonId, lobbyId);
|
||||
LOGGER.info("ButtonID: {}, LobbyID: {}", buttonId, lobbyId);
|
||||
|
||||
Reference in New Issue
Block a user