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

231 lines
7.3 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.client.data.*;
import envoy.client.event.EnvoyCloseEvent;
import envoy.client.net.Client;
import envoy.client.ui.SceneContext.SceneInfo;
import envoy.client.ui.controller.LoginScene;
import envoy.data.*;
import envoy.data.User.UserStatus;
import envoy.event.*;
import envoy.exception.EnvoyException;
import envoy.util.EnvoyLog;
import dev.kske.eventbus.EventBus;
/**
* Handles application startup and shutdown.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Startup.java</strong><br>
* Created: <strong>26.03.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;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.1-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 {
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 localDBDir = new File(config.getHomeDirectory(), config.getLocalDB().getPath());
logger.info("Initializing LocalDB at " + localDBDir);
localDB = new LocalDB(localDBDir);
} 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"));
// Create scene context
final var sceneContext = new SceneContext(stage);
context.setSceneContext(sceneContext);
// Authenticate with token if present
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
// Load login scene
sceneContext.load(SceneInfo.LOGIN_SCENE);
}
/**
* 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 cacheMap = new CacheMap();
cacheMap.put(Message.class, new Cache<Message>());
cacheMap.put(GroupMessage.class, new Cache<GroupMessage>());
cacheMap.put(MessageStatusChange.class, new Cache<MessageStatusChange>());
cacheMap.put(GroupMessageStatusChange.class, new Cache<GroupMessageStatusChange>());
try {
final var client = context.getClient();
client.performHandshake(credentials, cacheMap);
if (client.isOnline()) {
loadChatScene();
client.initReceiver(localDB, cacheMap);
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
localDB.setUser(client.getSender());
// 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();
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(SceneContext.SceneInfo.CHAT_SCENE);
stage.centerOnScreen();
if (StatusTrayIcon.isSupported()) {
// Configure hide on close
stage.setOnCloseRequest(e -> {
if (Settings.getInstance().isHideOnClose()) {
stage.setIconified(true);
e.consume();
} else EventBus.getInstance().dispatch(new EnvoyCloseEvent());
});
// Initialize status tray icon
final var trayIcon = new StatusTrayIcon(stage);
Settings.getInstance().getItems().get("hideOnClose").setChangeHandler(c -> {
if ((Boolean) c) trayIcon.show();
else trayIcon.hide();
});
}
}
}