package envoy.client.ui.controller; import java.io.FileNotFoundException; import java.io.IOException; import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.scene.control.*; import javafx.scene.control.Alert.AlertType; import envoy.client.data.Cache; import envoy.client.data.ClientConfig; import envoy.client.data.LocalDB; import envoy.client.net.Client; import envoy.client.ui.SceneContext; import envoy.client.ui.Startup; import envoy.data.LoginCredentials; import envoy.data.Message; import envoy.data.User; import envoy.data.User.UserStatus; import envoy.event.EventBus; import envoy.event.HandshakeRejection; import envoy.event.MessageStatusChange; import envoy.exception.EnvoyException; import envoy.util.EnvoyLog; /** * Project: envoy-client
* File: LoginDialog.java
* Created: 03.04.2020
* * @author Kai S. K. Engelbart * @author Maximilian Käfer * @since Envoy Client v0.1-beta */ public final class LoginScene { @FXML private TextField userTextField; @FXML private PasswordField passwordField; @FXML private PasswordField repeatPasswordField; @FXML private Label repeatPasswordLabel; @FXML private CheckBox registerCheckBox; @FXML private Label connectionLabel; private Client client; private LocalDB localDB; private Cache receivedMessageCache; private Cache receivedMessageStatusChangeCache; private SceneContext sceneContext; private static final Logger logger = EnvoyLog.getLogger(LoginScene.class); private static final EventBus eventBus = EventBus.getInstance(); private static final ClientConfig config = ClientConfig.getInstance(); @FXML private void initialize() { connectionLabel.setText("Server: " + config.getServer() + ":" + config.getPort()); // Show an alert after an unsuccessful handshake eventBus.register(HandshakeRejection.class, e -> Platform.runLater(() -> { new Alert(AlertType.ERROR, e.get()).showAndWait(); })); } /** * Loads the login dialog using the FXML file {@code LoginDialog.fxml}. * * @param client the client used to perform the * handshake * @param localDB the local database used for offline * login * @param receivedMessageCache the cache storing messages received * during * the handshake * @param receivedMessageStatusChangeCache the cache storing * messageStatusChangeEvents received * during handshake * @param sceneContext the scene context used to initialize * the chat * scene * @since Envoy Client v0.1-beta */ public void initializeData(Client client, LocalDB localDB, Cache receivedMessageCache, Cache receivedMessageStatusChangeCache, SceneContext sceneContext) { this.client = client; this.localDB = localDB; this.receivedMessageCache = receivedMessageCache; this.receivedMessageStatusChangeCache = receivedMessageStatusChangeCache; this.sceneContext = sceneContext; // Prepare handshake localDB.loadIDGenerator(); // Set initial cursor userTextField.requestFocus(); // Perform automatic login if configured if (config.hasLoginCredentials()) performHandshake(config.getLoginCredentials()); } @FXML private void loginButtonPressed() { // Prevent registration with unequal passwords if (registerCheckBox.isSelected() && !passwordField.getText().equals(repeatPasswordField.getText())) new Alert(AlertType.ERROR, "The entered password is unequal to the repeated one").showAndWait(); else performHandshake( new LoginCredentials(userTextField.getText(), passwordField.getText().toCharArray(), registerCheckBox.isSelected(), Startup.VERSION)); } @FXML private void offlineModeButtonPressed() { attemptOfflineMode(new LoginCredentials(userTextField.getText(), passwordField.getText().toCharArray(), false, Startup.VERSION)); } @FXML private void registerCheckboxChanged() { // Make repeat password field and label visible / invisible repeatPasswordField.setVisible(registerCheckBox.isSelected()); repeatPasswordLabel.setVisible(registerCheckBox.isSelected()); } @FXML private void abortLogin() { logger.log(Level.INFO, "The login process has been cancelled. Exiting..."); System.exit(0); } private void performHandshake(LoginCredentials credentials) { try { client.performHandshake(credentials, receivedMessageCache, receivedMessageStatusChangeCache); if (client.isOnline()) { client.initReceiver(localDB, receivedMessageCache, receivedMessageStatusChangeCache); loadChatScene(); } } catch (IOException | InterruptedException | TimeoutException e) { logger.log(Level.WARNING, "Could not connect to server: ", e); logger.log(Level.FINER, "Attempting offline mode..."); attemptOfflineMode(credentials); } } private void attemptOfflineMode(LoginCredentials credentials) { try { // Try entering offline mode localDB.loadUsers(); final User clientUser = (User) localDB.getUsers().get(credentials.getIdentifier()); if (clientUser == null) throw new EnvoyException("Could not enter offline mode: user name unknown"); client.setSender(clientUser); new Alert(AlertType.WARNING, "A connection to the server could not be established. Starting in offline mode.").showAndWait(); loadChatScene(); } catch (final Exception e) { new Alert(AlertType.ERROR, "Client error: " + e).showAndWait(); logger.log(Level.SEVERE, "Offline mode could not be loaded: ", e); System.exit(1); } } private void loadChatScene() { // Set client user in local database localDB.setUser(client.getSender()); // Initialize chats in local database try { localDB.initializeUserStorage(); localDB.loadUserData(); } catch (final FileNotFoundException e) { // The local database file has not yet been created, probably first login } catch (final Exception e) { e.printStackTrace(); new Alert(AlertType.ERROR, "Error while loading local database: " + e + "\nChats will not be stored locally.").showAndWait(); logger.log(Level.WARNING, "Could not load local database: ", e); } // Initialize write proxy final var writeProxy = client.createWriteProxy(localDB); if (client.isOnline()) { // Save all users to the local database and flush cache localDB.setUsers(client.getUsers()); localDB.createMissingChats(); writeProxy.flushCache(); } else // Set all contacts to offline mode localDB.getUsers().values().stream().filter(User.class::isInstance).map(User.class::cast).forEach(u -> u.setStatus(UserStatus.OFFLINE)); // Load ChatScene sceneContext.pop(); sceneContext.getStage().setMinHeight(400); sceneContext.getStage().setMinWidth(350); sceneContext.load(SceneContext.SceneInfo.CHAT_SCENE); sceneContext.getController().initializeData(sceneContext, localDB, client, writeProxy); // Relay unread messages from cache if (receivedMessageCache != null && client.isOnline()) receivedMessageCache.relay(); if (receivedMessageStatusChangeCache != null && client.isOnline()) receivedMessageStatusChangeCache.relay(); } }