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.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(); }
|
||||||
|
|
||||||
|
@ -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() {
|
||||||
|
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 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();
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user