This repository has been archived on 2021-12-05. You can view files and clone it, but cannot push or open issues or pull requests.
envoy/client/src/main/java/envoy/client/ui/Startup.java

247 lines
7.5 KiB
Java

package envoy.client.ui;
import java.io.*;
import java.time.Instant;
import java.util.concurrent.TimeoutException;
import java.util.logging.*;
import javafx.application.Application;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.stage.Stage;
import envoy.data.*;
import envoy.data.User.UserStatus;
import envoy.event.UserStatusChange;
import envoy.exception.EnvoyException;
import envoy.util.EnvoyLog;
import envoy.client.data.*;
import envoy.client.data.shortcuts.EnvoyShortcutConfig;
import envoy.client.helper.ShutdownHelper;
import envoy.client.net.Client;
import envoy.client.ui.controller.LoginScene;
import envoy.client.util.IconUtil;
/**
* Handles application startup.
*
* @author Kai S. K. Engelbart
* @author Maximilian Käfer
* @since Envoy Client v0.1-beta
*/
public final class Startup extends Application {
/**
* The version of this client. Used to verify compatibility with the server.
*
* @since Envoy Client v0.1-beta
*/
public static final String VERSION = "0.2-beta";
private static LocalDB localDB;
private static final Context context = Context.getInstance();
private static final Client client = context.getClient();
private static final ClientConfig config = ClientConfig.getInstance();
private static final Logger logger = EnvoyLog.getLogger(Startup.class);
/**
* Loads the configuration, initializes the client and the local database and delegates the rest
* of the startup process to {@link LoginScene}.
*
* @since Envoy Client v0.1-beta
*/
@Override
public void start(Stage stage) throws Exception {
// Initialize config and logger
try {
config.loadAll(Startup.class, "client.properties",
getParameters().getRaw().toArray(new String[0]));
EnvoyLog.initialize(config);
} catch (final IllegalStateException e) {
new Alert(AlertType.ERROR, "Error loading configuration values:\n" + e);
logger.log(Level.SEVERE, "Error loading configuration values: ", e);
System.exit(1);
}
logger.log(Level.INFO, "Envoy starting...");
// Initialize the local database
try {
final var localDBFile = new File(config.getHomeDirectory(), config.getServer());
logger.info("Initializing LocalDB at " + localDBFile);
localDB = new LocalDB(localDBFile);
} catch (IOException | EnvoyException e) {
logger.log(Level.SEVERE, "Could not initialize local database: ", e);
new Alert(AlertType.ERROR, "Could not initialize local database!\n" + e).showAndWait();
System.exit(1);
return;
}
// Prepare handshake
context.setLocalDB(localDB);
// Configure stage
stage.setTitle("Envoy");
stage.getIcons().add(IconUtil.loadIcon("envoy_logo"));
// Configure global shortcuts
EnvoyShortcutConfig.initializeEnvoyShortcuts();
// Create scene context
final var sceneContext = new SceneContext(stage);
context.setSceneContext(sceneContext);
// Authenticate with token if present or load login scene
if (localDB.getAuthToken() != null) {
logger.info("Attempting authentication with token...");
localDB.loadUserData();
if (!performHandshake(
LoginCredentials.loginWithToken(localDB.getUser().getName(), localDB.getAuthToken(),
VERSION, localDB.getLastSync())))
sceneContext.load(SceneInfo.LOGIN_SCENE);
} else
sceneContext.load(SceneInfo.LOGIN_SCENE);
stage.show();
}
/**
* Tries to perform a Handshake with the server.
*
* @param credentials the credentials to use for the handshake
* @return whether the handshake was successful or offline mode could be entered
* @since Envoy Client v0.2-beta
*/
public static boolean performHandshake(LoginCredentials credentials) {
final var originalStatus =
localDB.getUser() == null ? UserStatus.ONLINE : localDB.getUser().getStatus();
try {
client.performHandshake(credentials);
if (client.isOnline()) {
// Restore the original status as the server automatically returns status ONLINE
client.getSender().setStatus(originalStatus);
loadChatScene();
// Request an ID generator if none is present or the existing one is consumed
if (!localDB.hasIDGenerator() || !localDB.getIDGenerator().hasNext())
client.requestIDGenerator();
return true;
} else
return false;
} catch (IOException | InterruptedException | TimeoutException e) {
logger.log(Level.INFO, "Could not connect to server. Entering offline mode...");
return attemptOfflineMode(credentials.getIdentifier());
}
}
/**
* Attempts to load {@link envoy.client.ui.controller.ChatScene} in offline mode for a given
* user.
*
* @param identifier the identifier of the user - currently his username
* @return whether the offline mode could be entered
* @since Envoy Client v0.2-beta
*/
public static boolean attemptOfflineMode(String identifier) {
try {
// Try entering offline mode
final User clientUser = localDB.getUsers().get(identifier);
if (clientUser == null)
throw new EnvoyException("Could not enter offline mode: user name unknown");
client.setSender(clientUser);
loadChatScene();
return true;
} 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);
return false;
}
}
/**
* Loads the last known time a user has been online.
*
* @param identifier the identifier of this user - currently his name
* @return the last {@code Instant} at which he has been online
* @since Envoy Client v0.2-beta
*/
public static Instant loadLastSync(String identifier) {
try {
localDB.setUser(localDB.getUsers().get(identifier));
localDB.loadUserData();
} catch (final Exception e) {
// User storage empty, wrong user name etc. -> default lastSync
}
return localDB.getLastSync();
}
private static void loadChatScene() {
// Set client user in local database
final var user = client.getSender();
localDB.setUser(user);
// Initialize chats in local database
try {
localDB.loadUserData();
} catch (final FileNotFoundException e) {
// The local database file has not yet been created, probably first login
} catch (final Exception e) {
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);
}
context.initWriteProxy();
if (client.isOnline()) {
context.getWriteProxy().flushCache();
// Inform the server that this user has a different user status than expected
if (!user.getStatus().equals(UserStatus.ONLINE))
client.send(new UserStatusChange(user));
} else
// Set all contacts to offline mode
localDB.getChats()
.stream()
.map(Chat::getRecipient)
.filter(User.class::isInstance)
.map(User.class::cast)
.forEach(u -> u.setStatus(UserStatus.OFFLINE));
final var stage = context.getStage();
// Pop LoginScene if present
if (!context.getSceneContext().isEmpty())
context.getSceneContext().pop();
// Load ChatScene
stage.setMinHeight(400);
stage.setMinWidth(843);
context.getSceneContext().load(SceneInfo.CHAT_SCENE);
stage.centerOnScreen();
// Exit or minimize the stage when a close request occurs
stage.setOnCloseRequest(
e -> {
ShutdownHelper.exit();
if (Settings.getInstance().isHideOnClose() && StatusTrayIcon.isSupported())
e.consume();
});
// Initialize status tray icon
if (StatusTrayIcon.isSupported())
new StatusTrayIcon(stage).show();
// Start auto save thread
localDB.initAutoSave();
}
}