Fixed Bug Not Saving Values When Exiting via “Control”+”Q” #40
@@ -5,11 +5,13 @@ import java.nio.channels.*;
 | 
			
		||||
import java.nio.file.StandardOpenOption;
 | 
			
		||||
import java.time.Instant;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.logging.Level;
 | 
			
		||||
 | 
			
		||||
import envoy.client.event.EnvoyCloseEvent;
 | 
			
		||||
import envoy.data.*;
 | 
			
		||||
import envoy.event.*;
 | 
			
		||||
import envoy.exception.EnvoyException;
 | 
			
		||||
import envoy.util.SerializationUtils;
 | 
			
		||||
import envoy.util.*;
 | 
			
		||||
 | 
			
		||||
import dev.kske.eventbus.Event;
 | 
			
		||||
import dev.kske.eventbus.EventBus;
 | 
			
		||||
@@ -42,12 +44,13 @@ public final class LocalDB implements EventListener {
 | 
			
		||||
	private Instant lastSync = Instant.EPOCH;
 | 
			
		||||
 | 
			
		||||
	// Persistence
 | 
			
		||||
	private File		dbDir, userFile, idGeneratorFile, lastLoginFile, usersFile;
 | 
			
		||||
	private File		userFile;
 | 
			
		||||
	private FileLock	instanceLock;
 | 
			
		||||
 | 
			
		||||
	private final File dbDir, idGeneratorFile, lastLoginFile, usersFile;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Constructs an empty local database. To serialize any user-specific data to
 | 
			
		||||
	 * the file system, call {@link LocalDB#save(boolean)}.
 | 
			
		||||
	 * Constructs an empty local database.
 | 
			
		||||
	 *
 | 
			
		||||
	 * @param dbDir the directory in which to persist data
 | 
			
		||||
	 * @throws IOException    if {@code dbDir} is a file (and not a directory)
 | 
			
		||||
@@ -59,9 +62,8 @@ public final class LocalDB implements EventListener {
 | 
			
		||||
		EventBus.getInstance().registerListener(this);
 | 
			
		||||
 | 
			
		||||
		// Ensure that the database directory exists
 | 
			
		||||
		if (!dbDir.exists()) {
 | 
			
		||||
			dbDir.mkdirs();
 | 
			
		||||
		} else if (!dbDir.isDirectory()) throw new IOException(String.format("LocalDBDir '%s' is not a directory!", dbDir.getAbsolutePath()));
 | 
			
		||||
		if (!dbDir.exists()) dbDir.mkdirs();
 | 
			
		||||
		else if (!dbDir.isDirectory()) throw new IOException(String.format("LocalDBDir '%s' is not a directory!", dbDir.getAbsolutePath()));
 | 
			
		||||
 | 
			
		||||
		// Lock the directory
 | 
			
		||||
		lock();
 | 
			
		||||
@@ -88,12 +90,12 @@ public final class LocalDB implements EventListener {
 | 
			
		||||
	 * @since Envoy Client v0.2-beta
 | 
			
		||||
	 */
 | 
			
		||||
	private synchronized void lock() throws EnvoyException {
 | 
			
		||||
		File file = new File(dbDir, "instance.lock");
 | 
			
		||||
		final var file = new File(dbDir, "instance.lock");
 | 
			
		||||
		try {
 | 
			
		||||
			FileChannel fc = FileChannel.open(file.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE);
 | 
			
		||||
			final var fc = FileChannel.open(file.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE);
 | 
			
		||||
			instanceLock = fc.tryLock();
 | 
			
		||||
			if (instanceLock == null) throw new EnvoyException("Another Envoy instance is using this local database!");
 | 
			
		||||
		} catch (IOException e) {
 | 
			
		||||
		} catch (final IOException e) {
 | 
			
		||||
			throw new EnvoyException("Could not create lock file!", e);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -146,9 +148,8 @@ public final class LocalDB implements EventListener {
 | 
			
		||||
		users.put(user.getName(), user);
 | 
			
		||||
 | 
			
		||||
		// Synchronize user status data
 | 
			
		||||
		for (Contact contact : users.values())
 | 
			
		||||
			if (contact instanceof User)
 | 
			
		||||
				getChat(contact.getID()).ifPresent(chat -> { ((User) chat.getRecipient()).setStatus(((User) contact).getStatus()); });
 | 
			
		||||
		for (final var contact : users.values())
 | 
			
		||||
			if (contact instanceof User) getChat(contact.getID()).ifPresent(chat -> { ((User) chat.getRecipient()).setStatus(contact.getStatus()); });
 | 
			
		||||
 | 
			
		||||
		// Create missing chats
 | 
			
		||||
		user.getContacts()
 | 
			
		||||
@@ -162,26 +163,31 @@ public final class LocalDB implements EventListener {
 | 
			
		||||
	 * Stores all users. If the client user is specified, their chats will be stored
 | 
			
		||||
	 * as well. The message id generator will also be saved if present.
 | 
			
		||||
	 *
 | 
			
		||||
	 * @param isOnline determines which {@code lastSync} time stamp is saved
 | 
			
		||||
	 * @throws IOException if the saving process failed
 | 
			
		||||
	 * @since Envoy Client v0.3-alpha
 | 
			
		||||
	 */
 | 
			
		||||
	public synchronized void save(boolean isOnline) throws IOException {
 | 
			
		||||
	@Event(eventType = EnvoyCloseEvent.class, priority = 1000)
 | 
			
		||||
	private synchronized void save() {
 | 
			
		||||
		EnvoyLog.getLogger(LocalDB.class).log(Level.INFO, "Saving local database...");
 | 
			
		||||
 | 
			
		||||
		// Save users
 | 
			
		||||
		SerializationUtils.write(usersFile, users);
 | 
			
		||||
		try {
 | 
			
		||||
			SerializationUtils.write(usersFile, users);
 | 
			
		||||
 | 
			
		||||
		// Save user data and last sync time stamp
 | 
			
		||||
		if (user != null) SerializationUtils.write(userFile, chats, cacheMap, isOnline ? Instant.now() : lastSync);
 | 
			
		||||
			// Save user data and last sync time stamp
 | 
			
		||||
			if (user != null)
 | 
			
		||||
				SerializationUtils.write(userFile, chats, cacheMap, Context.getInstance().getClient().isOnline() ? Instant.now() : lastSync);
 | 
			
		||||
 | 
			
		||||
		// Save last login information
 | 
			
		||||
		if (authToken != null) SerializationUtils.write(lastLoginFile, user, authToken);
 | 
			
		||||
			// Save last login information
 | 
			
		||||
			if (authToken != null) SerializationUtils.write(lastLoginFile, user, authToken);
 | 
			
		||||
 | 
			
		||||
		// Save id generator
 | 
			
		||||
		if (hasIDGenerator()) SerializationUtils.write(idGeneratorFile, idGenerator);
 | 
			
		||||
			// Save ID generator
 | 
			
		||||
			if (hasIDGenerator()) SerializationUtils.write(idGeneratorFile, idGenerator);
 | 
			
		||||
		} catch (final IOException e) {
 | 
			
		||||
			EnvoyLog.getLogger(LocalDB.class).log(Level.SEVERE, "Unable to save local database: ", e);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	@Event
 | 
			
		||||
	private void onNewAuthToken(NewAuthToken evt) { authToken = evt.get(); }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,9 +2,14 @@ package envoy.client.data;
 | 
			
		||||
 | 
			
		||||
import java.io.*;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.logging.Level;
 | 
			
		||||
import java.util.prefs.Preferences;
 | 
			
		||||
 | 
			
		||||
import envoy.util.SerializationUtils;
 | 
			
		||||
import envoy.client.event.EnvoyCloseEvent;
 | 
			
		||||
import envoy.util.*;
 | 
			
		||||
 | 
			
		||||
import dev.kske.eventbus.*;
 | 
			
		||||
import dev.kske.eventbus.EventListener;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Manages all application settings, which are different objects that can be
 | 
			
		||||
@@ -20,7 +25,7 @@ import envoy.util.SerializationUtils;
 | 
			
		||||
 * @author Kai S. K. Engelbart
 | 
			
		||||
 * @since Envoy Client v0.2-alpha
 | 
			
		||||
 */
 | 
			
		||||
public final class Settings {
 | 
			
		||||
public final class Settings implements EventListener {
 | 
			
		||||
 | 
			
		||||
	// Actual settings accessible by the rest of the application
 | 
			
		||||
	private Map<String, SettingsItem<?>> items;
 | 
			
		||||
@@ -42,6 +47,8 @@ public final class Settings {
 | 
			
		||||
	 * @since Envoy Client v0.2-alpha
 | 
			
		||||
	 */
 | 
			
		||||
	private Settings() {
 | 
			
		||||
		EventBus.getInstance().registerListener(this);
 | 
			
		||||
 | 
			
		||||
		// Load settings from settings file
 | 
			
		||||
		try {
 | 
			
		||||
			items = SerializationUtils.read(settingsFile, HashMap.class);
 | 
			
		||||
@@ -65,10 +72,16 @@ public final class Settings {
 | 
			
		||||
	 * @throws IOException if an error occurs while saving the themes
 | 
			
		||||
	 * @since Envoy Client v0.2-alpha
 | 
			
		||||
	 */
 | 
			
		||||
	public void save() throws IOException {
 | 
			
		||||
	@Event(eventType = EnvoyCloseEvent.class, priority = 900)
 | 
			
		||||
	private void save() {
 | 
			
		||||
		EnvoyLog.getLogger(Settings.class).log(Level.INFO, "Saving settings...");
 | 
			
		||||
 | 
			
		||||
		// Save settings to settings file
 | 
			
		||||
		SerializationUtils.write(settingsFile, items);
 | 
			
		||||
		try {
 | 
			
		||||
			SerializationUtils.write(settingsFile, items);
 | 
			
		||||
		} catch (final IOException e) {
 | 
			
		||||
			EnvoyLog.getLogger(Settings.class).log(Level.SEVERE, "Unable to save settings file: ", e);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void supplementDefaults() {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								client/src/main/java/envoy/client/event/EnvoyCloseEvent.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								client/src/main/java/envoy/client/event/EnvoyCloseEvent.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
package envoy.client.event;
 | 
			
		||||
 | 
			
		||||
import envoy.event.Event.Valueless;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This event will be sent once Envoy is <strong>really</strong> closed.
 | 
			
		||||
 * Its purpose is to forcefully stop other threads peacefully so that the VM can
 | 
			
		||||
 * shutdown too.
 | 
			
		||||
 *
 | 
			
		||||
 * @author Leon Hofmeister
 | 
			
		||||
 * @since Envoy Client v0.2-beta
 | 
			
		||||
 */
 | 
			
		||||
public class EnvoyCloseEvent extends Valueless {
 | 
			
		||||
 | 
			
		||||
	private static final long serialVersionUID = 1L;
 | 
			
		||||
}
 | 
			
		||||
@@ -6,7 +6,7 @@ import java.util.concurrent.TimeoutException;
 | 
			
		||||
import java.util.logging.*;
 | 
			
		||||
 | 
			
		||||
import envoy.client.data.*;
 | 
			
		||||
import envoy.client.event.SendEvent;
 | 
			
		||||
import envoy.client.event.*;
 | 
			
		||||
import envoy.data.*;
 | 
			
		||||
import envoy.event.*;
 | 
			
		||||
import envoy.event.Event;
 | 
			
		||||
@@ -49,9 +49,7 @@ public final class Client implements EventListener, Closeable {
 | 
			
		||||
	 *
 | 
			
		||||
	 * @since Envoy Client v0.2-beta
 | 
			
		||||
	 */
 | 
			
		||||
	public Client() {
 | 
			
		||||
		eventBus.registerListener(this);
 | 
			
		||||
	}
 | 
			
		||||
	public Client() { eventBus.registerListener(this); }
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Enters the online mode by acquiring a user ID from the server. As a
 | 
			
		||||
@@ -236,7 +234,15 @@ public final class Client implements EventListener, Closeable {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void close() throws IOException { if (online) socket.close(); }
 | 
			
		||||
	@dev.kske.eventbus.Event(eventType = EnvoyCloseEvent.class, priority = 800)
 | 
			
		||||
	public void close() {
 | 
			
		||||
		if (online) {
 | 
			
		||||
			logger.log(Level.INFO, "Closing connection...");
 | 
			
		||||
			try {
 | 
			
		||||
				socket.close();
 | 
			
		||||
			} catch (final IOException e) {}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void writeObject(Object obj) throws IOException {
 | 
			
		||||
		checkOnline();
 | 
			
		||||
 
 | 
			
		||||
@@ -2,14 +2,11 @@ package envoy.client.net;
 | 
			
		||||
 | 
			
		||||
import java.io.*;
 | 
			
		||||
import java.net.SocketException;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import java.util.logging.Level;
 | 
			
		||||
import java.util.logging.Logger;
 | 
			
		||||
import java.util.logging.*;
 | 
			
		||||
 | 
			
		||||
import envoy.util.EnvoyLog;
 | 
			
		||||
import envoy.util.SerializationUtils;
 | 
			
		||||
import envoy.util.*;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Receives objects from the server and passes them to processor objects based
 | 
			
		||||
@@ -90,7 +87,7 @@ public final class Receiver extends Thread {
 | 
			
		||||
				}
 | 
			
		||||
			} catch (final SocketException | EOFException e) {
 | 
			
		||||
				// Connection probably closed by client.
 | 
			
		||||
				logger.log(Level.FINER, "Exiting receiver...");
 | 
			
		||||
				logger.log(Level.INFO, "Exiting receiver...");
 | 
			
		||||
				return;
 | 
			
		||||
			} catch (final Exception e) {
 | 
			
		||||
				logger.log(Level.SEVERE, "Error on receiver thread", e);
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ import javafx.scene.input.*;
 | 
			
		||||
import javafx.stage.Stage;
 | 
			
		||||
 | 
			
		||||
import envoy.client.data.Settings;
 | 
			
		||||
import envoy.client.event.ThemeChangeEvent;
 | 
			
		||||
import envoy.client.event.*;
 | 
			
		||||
import envoy.util.EnvoyLog;
 | 
			
		||||
 | 
			
		||||
import dev.kske.eventbus.*;
 | 
			
		||||
@@ -114,7 +114,10 @@ public final class SceneContext implements EventListener {
 | 
			
		||||
							// Presumably no Settings are loaded in the login scene, hence Envoy is closed
 | 
			
		||||
							// directly
 | 
			
		||||
							if (sceneInfo != SceneInfo.LOGIN_SCENE && settings.isHideOnClose()) stage.setIconified(true);
 | 
			
		||||
							else System.exit(0);
 | 
			
		||||
							else {
 | 
			
		||||
								EventBus.getInstance().dispatch(new EnvoyCloseEvent());
 | 
			
		||||
								System.exit(0);
 | 
			
		||||
							}
 | 
			
		||||
						});
 | 
			
		||||
 | 
			
		||||
			// The LoginScene is the only scene not intended to be resized
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ 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;
 | 
			
		||||
@@ -20,6 +21,8 @@ import envoy.event.*;
 | 
			
		||||
import envoy.exception.EnvoyException;
 | 
			
		||||
import envoy.util.EnvoyLog;
 | 
			
		||||
 | 
			
		||||
import dev.kske.eventbus.EventBus;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handles application startup and shutdown.
 | 
			
		||||
 * <p>
 | 
			
		||||
@@ -95,7 +98,7 @@ public final class Startup extends Application {
 | 
			
		||||
			if (!performHandshake(
 | 
			
		||||
					LoginCredentials.loginWithToken(localDB.getUser().getName(), localDB.getAuthToken(), VERSION, localDB.getLastSync())))
 | 
			
		||||
				sceneContext.load(SceneInfo.LOGIN_SCENE);
 | 
			
		||||
		} else 
 | 
			
		||||
		} else
 | 
			
		||||
			// Load login scene
 | 
			
		||||
			sceneContext.load(SceneInfo.LOGIN_SCENE);
 | 
			
		||||
	}
 | 
			
		||||
@@ -213,7 +216,7 @@ public final class Startup extends Application {
 | 
			
		||||
				if (Settings.getInstance().isHideOnClose()) {
 | 
			
		||||
					stage.setIconified(true);
 | 
			
		||||
					e.consume();
 | 
			
		||||
				}
 | 
			
		||||
				} else EventBus.getInstance().dispatch(new EnvoyCloseEvent());
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			// Initialize status tray icon
 | 
			
		||||
@@ -224,25 +227,4 @@ public final class Startup extends Application {
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Closes the client connection and saves the local database and settings.
 | 
			
		||||
	 *
 | 
			
		||||
	 * @since Envoy Client v0.1-beta
 | 
			
		||||
	 */
 | 
			
		||||
	@Override
 | 
			
		||||
	public void stop() {
 | 
			
		||||
		try {
 | 
			
		||||
			logger.log(Level.INFO, "Saving local database and settings...");
 | 
			
		||||
			localDB.save(client.isOnline());
 | 
			
		||||
			Settings.getInstance().save();
 | 
			
		||||
 | 
			
		||||
			if (client.isOnline()) logger.log(Level.INFO, "Closing connection...");
 | 
			
		||||
			client.close();
 | 
			
		||||
 | 
			
		||||
			logger.log(Level.INFO, "Envoy was terminated by its user");
 | 
			
		||||
		} catch (final Exception e) {
 | 
			
		||||
			logger.log(Level.SEVERE, "Unable to save local files: ", e);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user