Fixed Bug Not Saving Values When Exiting via “Control”+”Q” (#40)

Fixed bug not saving values when exiting via "Control"+"Q"
Reviewed-on: https://git.kske.dev/zdm/envoy/pulls/40
Reviewed-by: kske <kai@kske.dev>
This commit is contained in:
Leon Hofmeister 2020-09-22 14:42:51 +02:00
parent 8ed6faca96
commit 1b60ab3f0d
7 changed files with 87 additions and 64 deletions

View File

@ -5,11 +5,13 @@ import java.nio.channels.*;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.time.Instant; import java.time.Instant;
import java.util.*; import java.util.*;
import java.util.logging.Level;
import envoy.client.event.EnvoyCloseEvent;
import envoy.data.*; import envoy.data.*;
import envoy.event.*; import envoy.event.*;
import envoy.exception.EnvoyException; import envoy.exception.EnvoyException;
import envoy.util.SerializationUtils; import envoy.util.*;
import dev.kske.eventbus.Event; import dev.kske.eventbus.Event;
import dev.kske.eventbus.EventBus; import dev.kske.eventbus.EventBus;
@ -42,12 +44,13 @@ public final class LocalDB implements EventListener {
private Instant lastSync = Instant.EPOCH; private Instant lastSync = Instant.EPOCH;
// Persistence // Persistence
private File dbDir, userFile, idGeneratorFile, lastLoginFile, usersFile; private File userFile;
private FileLock instanceLock; private FileLock instanceLock;
private final File dbDir, idGeneratorFile, lastLoginFile, usersFile;
/** /**
* Constructs an empty local database. To serialize any user-specific data to * Constructs an empty local database.
* the file system, call {@link LocalDB#save(boolean)}.
* *
* @param dbDir the directory in which to persist data * @param dbDir the directory in which to persist data
* @throws IOException if {@code dbDir} is a file (and not a directory) * @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); EventBus.getInstance().registerListener(this);
// Ensure that the database directory exists // Ensure that the database directory exists
if (!dbDir.exists()) { if (!dbDir.exists()) dbDir.mkdirs();
dbDir.mkdirs(); else if (!dbDir.isDirectory()) throw new IOException(String.format("LocalDBDir '%s' is not a directory!", dbDir.getAbsolutePath()));
} else if (!dbDir.isDirectory()) throw new IOException(String.format("LocalDBDir '%s' is not a directory!", dbDir.getAbsolutePath()));
// Lock the directory // Lock the directory
lock(); lock();
@ -88,12 +90,12 @@ public final class LocalDB implements EventListener {
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
private synchronized void lock() throws EnvoyException { private synchronized void lock() throws EnvoyException {
File file = new File(dbDir, "instance.lock"); final var file = new File(dbDir, "instance.lock");
try { 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(); instanceLock = fc.tryLock();
if (instanceLock == null) throw new EnvoyException("Another Envoy instance is using this local database!"); 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); throw new EnvoyException("Could not create lock file!", e);
} }
} }
@ -146,9 +148,8 @@ public final class LocalDB implements EventListener {
users.put(user.getName(), user); users.put(user.getName(), user);
// Synchronize user status data // Synchronize user status data
for (Contact contact : users.values()) for (final var contact : users.values())
if (contact instanceof User) if (contact instanceof User) getChat(contact.getID()).ifPresent(chat -> { ((User) chat.getRecipient()).setStatus(contact.getStatus()); });
getChat(contact.getID()).ifPresent(chat -> { ((User) chat.getRecipient()).setStatus(((User) contact).getStatus()); });
// Create missing chats // Create missing chats
user.getContacts() 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 * 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. * 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 * @throws IOException if the saving process failed
* @since Envoy Client v0.3-alpha * @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 // Save users
SerializationUtils.write(usersFile, users); try {
SerializationUtils.write(usersFile, users);
// Save user data and last sync time stamp // Save user data and last sync time stamp
if (user != null) SerializationUtils.write(userFile, chats, cacheMap, isOnline ? Instant.now() : lastSync); if (user != null)
SerializationUtils.write(userFile, chats, cacheMap, Context.getInstance().getClient().isOnline() ? Instant.now() : lastSync);
// Save last login information // Save last login information
if (authToken != null) SerializationUtils.write(lastLoginFile, user, authToken); if (authToken != null) SerializationUtils.write(lastLoginFile, user, authToken);
// Save id generator // Save ID generator
if (hasIDGenerator()) SerializationUtils.write(idGeneratorFile, idGenerator); if (hasIDGenerator()) SerializationUtils.write(idGeneratorFile, idGenerator);
} catch (final IOException e) {
EnvoyLog.getLogger(LocalDB.class).log(Level.SEVERE, "Unable to save local database: ", e);
}
} }
@Event @Event
private void onNewAuthToken(NewAuthToken evt) { authToken = evt.get(); } private void onNewAuthToken(NewAuthToken evt) { authToken = evt.get(); }

View File

@ -2,9 +2,14 @@ package envoy.client.data;
import java.io.*; import java.io.*;
import java.util.*; import java.util.*;
import java.util.logging.Level;
import java.util.prefs.Preferences; 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 * Manages all application settings, which are different objects that can be
@ -20,7 +25,7 @@ import envoy.util.SerializationUtils;
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.2-alpha * @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 // Actual settings accessible by the rest of the application
private Map<String, SettingsItem<?>> items; private Map<String, SettingsItem<?>> items;
@ -42,6 +47,8 @@ public final class Settings {
* @since Envoy Client v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
private Settings() { private Settings() {
EventBus.getInstance().registerListener(this);
// Load settings from settings file // Load settings from settings file
try { try {
items = SerializationUtils.read(settingsFile, HashMap.class); items = SerializationUtils.read(settingsFile, HashMap.class);
@ -65,10 +72,16 @@ public final class Settings {
* @throws IOException if an error occurs while saving the themes * @throws IOException if an error occurs while saving the themes
* @since Envoy Client v0.2-alpha * @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 // 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() { private void supplementDefaults() {

View 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;
}

View File

@ -6,7 +6,7 @@ import java.util.concurrent.TimeoutException;
import java.util.logging.*; import java.util.logging.*;
import envoy.client.data.*; import envoy.client.data.*;
import envoy.client.event.SendEvent; import envoy.client.event.*;
import envoy.data.*; import envoy.data.*;
import envoy.event.*; import envoy.event.*;
import envoy.event.Event; import envoy.event.Event;
@ -49,9 +49,7 @@ public final class Client implements EventListener, Closeable {
* *
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
public Client() { public Client() { eventBus.registerListener(this); }
eventBus.registerListener(this);
}
/** /**
* Enters the online mode by acquiring a user ID from the server. As a * 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 @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 { private void writeObject(Object obj) throws IOException {
checkOnline(); checkOnline();

View File

@ -2,14 +2,11 @@ package envoy.client.net;
import java.io.*; import java.io.*;
import java.net.SocketException; import java.net.SocketException;
import java.util.HashMap; import java.util.*;
import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.logging.Level; import java.util.logging.*;
import java.util.logging.Logger;
import envoy.util.EnvoyLog; import envoy.util.*;
import envoy.util.SerializationUtils;
/** /**
* Receives objects from the server and passes them to processor objects based * 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) { } catch (final SocketException | EOFException e) {
// Connection probably closed by client. // Connection probably closed by client.
logger.log(Level.FINER, "Exiting receiver..."); logger.log(Level.INFO, "Exiting receiver...");
return; return;
} catch (final Exception e) { } catch (final Exception e) {
logger.log(Level.SEVERE, "Error on receiver thread", e); logger.log(Level.SEVERE, "Error on receiver thread", e);

View File

@ -11,7 +11,7 @@ import javafx.scene.input.*;
import javafx.stage.Stage; import javafx.stage.Stage;
import envoy.client.data.Settings; import envoy.client.data.Settings;
import envoy.client.event.ThemeChangeEvent; import envoy.client.event.*;
import envoy.util.EnvoyLog; import envoy.util.EnvoyLog;
import dev.kske.eventbus.*; 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 // Presumably no Settings are loaded in the login scene, hence Envoy is closed
// directly // directly
if (sceneInfo != SceneInfo.LOGIN_SCENE && settings.isHideOnClose()) stage.setIconified(true); 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 // The LoginScene is the only scene not intended to be resized

View File

@ -11,6 +11,7 @@ import javafx.scene.control.Alert.AlertType;
import javafx.stage.Stage; import javafx.stage.Stage;
import envoy.client.data.*; import envoy.client.data.*;
import envoy.client.event.EnvoyCloseEvent;
import envoy.client.net.Client; import envoy.client.net.Client;
import envoy.client.ui.SceneContext.SceneInfo; import envoy.client.ui.SceneContext.SceneInfo;
import envoy.client.ui.controller.LoginScene; import envoy.client.ui.controller.LoginScene;
@ -20,6 +21,8 @@ import envoy.event.*;
import envoy.exception.EnvoyException; import envoy.exception.EnvoyException;
import envoy.util.EnvoyLog; import envoy.util.EnvoyLog;
import dev.kske.eventbus.EventBus;
/** /**
* Handles application startup and shutdown. * Handles application startup and shutdown.
* <p> * <p>
@ -95,7 +98,7 @@ public final class Startup extends Application {
if (!performHandshake( if (!performHandshake(
LoginCredentials.loginWithToken(localDB.getUser().getName(), localDB.getAuthToken(), VERSION, localDB.getLastSync()))) LoginCredentials.loginWithToken(localDB.getUser().getName(), localDB.getAuthToken(), VERSION, localDB.getLastSync())))
sceneContext.load(SceneInfo.LOGIN_SCENE); sceneContext.load(SceneInfo.LOGIN_SCENE);
} else } else
// Load login scene // Load login scene
sceneContext.load(SceneInfo.LOGIN_SCENE); sceneContext.load(SceneInfo.LOGIN_SCENE);
} }
@ -213,7 +216,7 @@ public final class Startup extends Application {
if (Settings.getInstance().isHideOnClose()) { if (Settings.getInstance().isHideOnClose()) {
stage.setIconified(true); stage.setIconified(true);
e.consume(); e.consume();
} } else EventBus.getInstance().dispatch(new EnvoyCloseEvent());
}); });
// Initialize status tray icon // 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);
}
}
} }