Move Envoy Client to client/ subdirectory

This commit is contained in:
2020-07-13 11:37:45 +02:00
parent 0c4d807e41
commit 0309d0d860
105 changed files with 0 additions and 0 deletions

View File

@ -0,0 +1,65 @@
package envoy.client.data;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.Queue;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import envoy.util.EnvoyLog;
/**
* Stores elements in a queue to process them later.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Cache.java</strong><br>
* Created: <strong>6 Feb 2020</strong><br>
*
* @param <T> the type of cached elements
* @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha
*/
public final class Cache<T> implements Consumer<T>, Serializable {
private final Queue<T> elements = new LinkedList<>();
private transient Consumer<T> processor;
private static final Logger logger = EnvoyLog.getLogger(Cache.class);
private static final long serialVersionUID = 0L;
/**
* Adds an element to the cache.
*
* @param element the element to add
* @since Envoy Client v0.3-alpha
*/
@Override
public void accept(T element) {
logger.log(Level.FINE, String.format("Adding element %s to cache", element));
elements.offer(element);
}
@Override
public String toString() { return String.format("Cache[elements=" + elements + "]"); }
/**
* Sets the processor to which cached elements are relayed.
*
* @param processor the processor to set
* @since Envoy Client v0.3-alpha
*/
public void setProcessor(Consumer<T> processor) { this.processor = processor; }
/**
* Relays all cached elements to the processor.
*
* @throws IllegalStateException if the processor is not initialized
* @since Envoy Client v0.3-alpha
*/
public void relay() {
if (processor == null) throw new IllegalStateException("Processor is not defined");
elements.forEach(processor::accept);
elements.clear();
}
}

View File

@ -0,0 +1,66 @@
package envoy.client.data;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
/**
* Stores a heterogeneous map of {@link Cache} objects with different type
* parameters.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>CacheMap.java</strong><br>
* Created: <strong>09.07.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public final class CacheMap implements Serializable {
private final Map<Class<?>, Cache<?>> map = new HashMap<>();
private static final long serialVersionUID = 1L;
/**
* Adds a cache to the map.
*
* @param <T> the type accepted by the cache
* @param key the class that maps to the cache
* @param cache the cache to store
* @since Envoy Client v0.1-beta
*/
public <T> void put(Class<T> key, Cache<T> cache) { map.put(key, cache); }
/**
* Returns a cache mapped by a class.
*
* @param <T> the type accepted by the cache
* @param key the class that maps to the cache
* @return the cache
* @since Envoy Client v0.1-beta
*/
public <T> Cache<T> get(Class<T> key) { return (Cache<T>) map.get(key); }
/**
* Returns a cache mapped by a class or any of its subclasses.
*
* @param <T> the type accepted by the cache
* @param key the class that maps to the cache
* @return the cache
* @since Envoy Client v0.1-beta
*/
public <T> Cache<? super T> getApplicable(Class<T> key) {
Cache<? super T> cache = get(key);
if (cache == null)
for (var e : map.entrySet())
if (e.getKey().isAssignableFrom(key))
cache = (Cache<? super T>) e.getValue();
return cache;
}
/**
* @return the map in which the caches are stored
* @since Envoy Client v0.1-beta
*/
public Map<Class<?>, Cache<?>> getMap() { return map; }
}

View File

@ -0,0 +1,153 @@
package envoy.client.data;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import envoy.client.net.WriteProxy;
import envoy.data.*;
import envoy.data.Message.MessageStatus;
import envoy.event.MessageStatusChange;
/**
* Represents a chat between two {@link User}s
* as a list of {@link Message} objects.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Chat.java</strong><br>
* Created: <strong>19 Oct 2019</strong><br>
*
* @author Maximilian K&auml;fer
* @author Leon Hofmeister
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-alpha
*/
public class Chat implements Serializable {
protected final Contact recipient;
protected final List<Message> messages = new ArrayList<>();
protected int unreadAmount;
private static final long serialVersionUID = 1L;
/**
* Provides the list of messages that the recipient receives.
* <p>
* Saves the Messages in the corresponding chat at that Point.
*
* @param recipient the user who receives the messages
* @since Envoy Client v0.1-alpha
*/
public Chat(Contact recipient) {
this.recipient = recipient;
}
@Override
public String toString() { return String.format("Chat[recipient=%s,messages=%d]", recipient, messages.size()); }
/**
* Generates a hash code based on the recipient.
*
* @since Envoy Client v0.1-beta
*/
@Override
public int hashCode() { return Objects.hash(recipient); }
/**
* Tests equality to another object based on the recipient.
*
* @since Envoy Client v0.1-beta
*/
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Chat)) return false;
Chat other = (Chat) obj;
return Objects.equals(recipient, other.recipient);
}
/**
* Sets the status of all chat messages received from the recipient to
* {@code READ} starting from the bottom and stopping once a read message is
* found.
*
* @param writeProxy the write proxy instance used to notify the server about
* the message status changes
* @throws IOException if a {@link MessageStatusChange} could not be
* delivered to the server
* @since Envoy Client v0.3-alpha
*/
public void read(WriteProxy writeProxy) throws IOException {
for (int i = messages.size() - 1; i >= 0; --i) {
final Message m = messages.get(i);
if (m.getSenderID() == recipient.getID()) if (m.getStatus() == MessageStatus.READ) break;
else {
m.setStatus(MessageStatus.READ);
writeProxy.writeMessageStatusChange(new MessageStatusChange(m));
}
}
unreadAmount = 0;
}
/**
* @return {@code true} if the newest message received in the chat doesn't have
* the status {@code READ}
* @since Envoy Client v0.3-alpha
*/
public boolean isUnread() { return !messages.isEmpty() && messages.get(messages.size() - 1).getStatus() != MessageStatus.READ; }
/**
* Inserts a message at the correct place according to its creation date.
*
* @param message the message to insert
* @since Envoy Client v0.1-beta
*/
public void insert(Message message) {
for (int i = messages.size() - 1; i >= 0; --i)
if (message.getCreationDate().isAfter(messages.get(i).getCreationDate())) {
messages.add(i + 1, message);
return;
}
messages.add(0, message);
}
/**
* Increments the amount of unread messages.
*
* @since Envoy Client v0.1-beta
*/
public void incrementUnreadAmount() { unreadAmount++; }
/**
* @return the amount of unread mesages in this chat
* @since Envoy Client v0.1-beta
*/
public int getUnreadAmount() { return unreadAmount; }
/**
* @return all messages in the current chat
* @since Envoy Client v0.1-beta
*/
public List<Message> getMessages() { return messages; }
/**
* @return the recipient of a message
* @since Envoy Client v0.1-alpha
*/
public Contact getRecipient() { return recipient; }
/**
* @return whether this {@link Chat} points at a {@link User}
* @since Envoy Client v0.1-beta
*/
public boolean isUserChat() { return recipient instanceof User; }
/**
* @return whether this {@link Chat} points at a {@link Group}
* @since Envoy Client v0.1-beta
*/
public boolean isGroupChat() { return recipient instanceof Group; }
}

View File

@ -0,0 +1,115 @@
package envoy.client.data;
import static java.util.function.Function.identity;
import java.io.File;
import java.util.logging.Level;
import envoy.client.ui.Startup;
import envoy.data.Config;
import envoy.data.ConfigItem;
import envoy.data.LoginCredentials;
/**
* Implements a configuration specific to the Envoy Client with default values
* and convenience methods.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ClientConfig.java</strong><br>
* Created: <strong>01.03.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class ClientConfig extends Config {
private static ClientConfig config;
/**
* @return the singleton instance of the client config
* @since Envoy Client v0.1-beta
*/
public static ClientConfig getInstance() {
if (config == null) config = new ClientConfig();
return config;
}
private ClientConfig() {
items.put("server", new ConfigItem<>("server", "s", identity(), null, true));
items.put("port", new ConfigItem<>("port", "p", Integer::parseInt, null, true));
items.put("localDB", new ConfigItem<>("localDB", "db", File::new, new File("localDB"), true));
items.put("ignoreLocalDB", new ConfigItem<>("ignoreLocalDB", "nodb", Boolean::parseBoolean, false, false));
items.put("homeDirectory", new ConfigItem<>("homeDirectory", "h", File::new, new File(System.getProperty("user.home"), ".envoy"), true));
items.put("fileLevelBarrier", new ConfigItem<>("fileLevelBarrier", "fb", Level::parse, Level.CONFIG, true));
items.put("consoleLevelBarrier", new ConfigItem<>("consoleLevelBarrier", "cb", Level::parse, Level.FINEST, true));
items.put("user", new ConfigItem<>("user", "u", identity()));
items.put("password", new ConfigItem<>("password", "pw", identity()));
}
/**
* @return the host name of the Envoy server
* @since Envoy Client v0.1-alpha
*/
public String getServer() { return (String) items.get("server").get(); }
/**
* @return the port at which the Envoy server is located on the host
* @since Envoy Client v0.1-alpha
*/
public Integer getPort() { return (Integer) items.get("port").get(); }
/**
* @return the local database specific to the client user
* @since Envoy Client v0.1-alpha
*/
public File getLocalDB() { return (File) items.get("localDB").get(); }
/**
* @return {@code true} if the local database is to be ignored
* @since Envoy Client v0.3-alpha
*/
public Boolean isIgnoreLocalDB() { return (Boolean) items.get("ignoreLocalDB").get(); }
/**
* @return the directory in which all local files are saves
* @since Envoy Client v0.2-alpha
*/
public File getHomeDirectory() { return (File) items.get("homeDirectory").get(); }
/**
* @return the minimal {@link Level} to log inside the log file
* @since Envoy Client v0.2-alpha
*/
public Level getFileLevelBarrier() { return (Level) items.get("fileLevelBarrier").get(); }
/**
* @return the minimal {@link Level} to log inside the console
* @since Envoy Client v0.2-alpha
*/
public Level getConsoleLevelBarrier() { return (Level) items.get("consoleLevelBarrier").get(); }
/**
* @return the user name
* @since Envoy Client v0.3-alpha
*/
public String getUser() { return (String) items.get("user").get(); }
/**
* @return the password
* @since Envoy Client v0.3-alpha
*/
public String getPassword() { return (String) items.get("password").get(); }
/**
* @return {@code true} if user name and password are set
* @since Envoy Client v0.3-alpha
*/
public boolean hasLoginCredentials() { return getUser() != null && getPassword() != null; }
/**
* @return login credentials for the specified user name and password, without
* the registration option
* @since Envoy Client v0.3-alpha
*/
public LoginCredentials getLoginCredentials() { return new LoginCredentials(getUser(), getPassword(), false, Startup.VERSION); }
}

View File

@ -0,0 +1,55 @@
package envoy.client.data;
import java.io.IOException;
import java.time.LocalDateTime;
import envoy.client.net.WriteProxy;
import envoy.data.Contact;
import envoy.data.GroupMessage;
import envoy.data.Message.MessageStatus;
import envoy.data.User;
import envoy.event.GroupMessageStatusChange;
/**
* Represents a chat between a user and a group
* as a list of messages.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>GroupChat.java</strong><br>
* Created: <strong>05.07.2020</strong><br>
*
* @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta
*/
public class GroupChat extends Chat {
private final User sender;
private static final long serialVersionUID = 1L;
/**
* @param sender the user sending the messages
* @param recipient the group whose members receive the messages
* @since Envoy Client v0.1-beta
*/
public GroupChat(User sender, Contact recipient) {
super(recipient);
this.sender = sender;
}
@Override
public void read(WriteProxy writeProxy) throws IOException {
for (int i = messages.size() - 1; i >= 0; --i) {
final GroupMessage gmsg = (GroupMessage) messages.get(i);
if (gmsg.getSenderID() != sender.getID()) {
if (gmsg.getMemberStatuses().get(sender.getID()) == MessageStatus.READ) break;
else {
gmsg.getMemberStatuses().replace(sender.getID(), MessageStatus.READ);
writeProxy
.writeMessageStatusChange(new GroupMessageStatusChange(gmsg.getID(), MessageStatus.READ, LocalDateTime.now(), sender.getID()));
}
}
}
unreadAmount = 0;
}
}

View File

@ -0,0 +1,205 @@
package envoy.client.data;
import java.util.*;
import envoy.data.*;
import envoy.event.GroupResize;
import envoy.event.MessageStatusChange;
import envoy.event.NameChange;
/**
* Stores information about the current {@link User} and their {@link Chat}s.
* For message ID generation a {@link IDGenerator} is stored as well.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>LocalDB.java</strong><br>
* Created: <strong>3 Feb 2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha
*/
public abstract class LocalDB {
protected User user;
protected Map<String, Contact> users = new HashMap<>();
protected List<Chat> chats = new ArrayList<>();
protected IDGenerator idGenerator;
protected CacheMap cacheMap = new CacheMap();
{
cacheMap.put(Message.class, new Cache<>());
cacheMap.put(MessageStatusChange.class, new Cache<>());
}
/**
* Initializes a storage space for a user-specific list of chats.
*
* @since Envoy Client v0.3-alpha
*/
public void initializeUserStorage() {}
/**
* 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.
*
* @throws Exception if the saving process failed
* @since Envoy Client v0.3-alpha
*/
public void save() throws Exception {}
/**
* Loads all user data.
*
* @throws Exception if the loading process failed
* @since Envoy Client v0.3-alpha
*/
public void loadUsers() throws Exception {}
/**
* Loads all data of the client user.
*
* @throws Exception if the loading process failed
* @since Envoy Client v0.3-alpha
*/
public void loadUserData() throws Exception {}
/**
* Loads the ID generator. Any exception thrown during this process is ignored.
*
* @since Envoy Client v0.3-alpha
*/
public void loadIDGenerator() {}
/**
* Synchronizes the contact list of the client user with the chat and user
* storage.
*
* @since Envoy Client v0.1-beta
*/
public void synchronize() {
user.getContacts().stream().filter(u -> u instanceof User && !users.containsKey(u.getName())).forEach(u -> users.put(u.getName(), u));
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()); });
// Create missing chats
user.getContacts()
.stream()
.filter(c -> !c.equals(user) && getChat(c.getID()).isEmpty())
.map(c -> c instanceof User ? new Chat(c) : new GroupChat(user, c))
.forEach(chats::add);
}
/**
* @return a {@code Map<String, User>} of all users stored locally with their
* user names as keys
* @since Envoy Client v0.2-alpha
*/
public Map<String, Contact> getUsers() { return users; }
/**
* @return all saved {@link Chat} objects that list the client user as the
* sender
* @since Envoy Client v0.1-alpha
**/
public List<Chat> getChats() { return chats; }
/**
* @param chats the chats to set
*/
public void setChats(List<Chat> chats) { this.chats = chats; }
/**
* @return the {@link User} who initialized the local database
* @since Envoy Client v0.2-alpha
*/
public User getUser() { return user; }
/**
* @param user the user to set
* @since Envoy Client v0.2-alpha
*/
public void setUser(User user) { this.user = user; }
/**
* @return the message ID generator
* @since Envoy Client v0.3-alpha
*/
public IDGenerator getIDGenerator() { return idGenerator; }
/**
* @param idGenerator the message ID generator to set
* @since Envoy Client v0.3-alpha
*/
public void setIDGenerator(IDGenerator idGenerator) { this.idGenerator = idGenerator; }
/**
* @return {@code true} if an {@link IDGenerator} is present
* @since Envoy Client v0.3-alpha
*/
public boolean hasIDGenerator() { return idGenerator != null; }
/**
* @return the cache map for messages and message status changes
* @since Envoy Client v0.1-beta
*/
public CacheMap getCacheMap() { return cacheMap; }
/**
* Searches for a message by ID.
*
* @param id the ID of the message to search for
* @return an optional containing the message
* @since Envoy Client v0.1-beta
*/
public Optional<Message> getMessage(long id) {
return chats.stream().map(Chat::getMessages).flatMap(List::stream).filter(m -> m.getID() == id).findAny();
}
/**
* Searches for a chat by recipient ID.
*
* @param recipientID the ID of the chat's recipient
* @return an optional containing the chat
* @since Envoy Client v0.1-beta
*/
public Optional<Chat> getChat(long recipientID) { return chats.stream().filter(c -> c.getRecipient().getID() == recipientID).findAny(); }
/**
* Performs a contact name change if the corresponding contact is present.
*
* @param event the {@link NameChange} to process
* @since Envoy Client v0.1-beta
*/
public void replaceContactName(NameChange event) {
chats.stream().map(Chat::getRecipient).filter(c -> c.getID() == event.getID()).findAny().ifPresent(c -> c.setName(event.get()));
}
/**
* Performs a group resize operation if the corresponding group is present.
*
* @param event the {@link GroupResize} to process
* @since Envoy Client v0.1-beta
*/
public void updateGroup(GroupResize event) {
chats.stream()
.map(Chat::getRecipient)
.filter(Group.class::isInstance)
.filter(g -> g.getID() == event.getGroupID() && g.getID() != user.getID())
.map(Group.class::cast)
.findAny()
.ifPresent(group -> {
switch (event.getOperation()) {
case ADD:
group.getContacts().add(event.get());
break;
case REMOVE:
group.getContacts().remove(event.get());
break;
}
});
}
}

View File

@ -0,0 +1,88 @@
package envoy.client.data;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import envoy.data.IDGenerator;
import envoy.util.SerializationUtils;
/**
* Implements a {@link LocalDB} in a way that stores all information inside a
* folder on the local file system.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>PersistentLocalDB.java</strong><br>
* Created: <strong>27.10.2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy Client v0.1-alpha
*/
public final class PersistentLocalDB extends LocalDB {
private File dbDir, userFile, idGeneratorFile, usersFile;
/**
* Constructs an empty local database. To serialize any user-specific data to
* the file system, call {@link PersistentLocalDB#initializeUserStorage()} first
* and then {@link PersistentLocalDB#save()}.
*
* @param dbDir the directory in which to persist data
* @throws IOException if {@code dbDir} is a file (and not a directory)
* @since Envoy Client v0.1-alpha
*/
public PersistentLocalDB(File dbDir) throws IOException {
this.dbDir = dbDir;
// Test if the database directory is actually a directory
if (dbDir.exists() && !dbDir.isDirectory())
throw new IOException(String.format("LocalDBDir '%s' is not a directory!", dbDir.getAbsolutePath()));
// Initialize global files
idGeneratorFile = new File(dbDir, "id_gen.db");
usersFile = new File(dbDir, "users.db");
}
/**
* Creates a database file for a user-specific list of chats.
*
* @throws IllegalStateException if the client user is not specified
* @since Envoy Client v0.1-alpha
*/
@Override
public void initializeUserStorage() {
if (user == null) throw new IllegalStateException("Client user is null, cannot initialize user storage");
userFile = new File(dbDir, user.getID() + ".db");
}
@Override
public void save() throws IOException {
// Save users
SerializationUtils.write(usersFile, users);
// Save user data
if (user != null) SerializationUtils.write(userFile, chats, cacheMap);
// Save id generator
if (hasIDGenerator()) SerializationUtils.write(idGeneratorFile, idGenerator);
}
@Override
public void loadUsers() throws ClassNotFoundException, IOException { users = SerializationUtils.read(usersFile, HashMap.class); }
@Override
public void loadUserData() throws ClassNotFoundException, IOException {
try (var in = new ObjectInputStream(new FileInputStream(userFile))) {
chats = (ArrayList<Chat>) in.readObject();
cacheMap = (CacheMap) in.readObject();
}
}
@Override
public void loadIDGenerator() {
try {
idGenerator = SerializationUtils.read(idGeneratorFile, IDGenerator.class);
} catch (ClassNotFoundException | IOException e) {}
}
}

View File

@ -0,0 +1,146 @@
package envoy.client.data;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.prefs.Preferences;
import envoy.util.SerializationUtils;
/**
* Manages all application settings, which are different objects that can be
* changed during runtime and serialized them by using either the file system or
* the {@link Preferences} API.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Settings.java</strong><br>
* Created: <strong>11 Nov 2019</strong><br>
*
* @author Leon Hofmeister
* @author Maximilian K&auml;fer
* @author Kai S. K. Engelbart
* @since Envoy Client v0.2-alpha
*/
public class Settings {
// Actual settings accessible by the rest of the application
private Map<String, SettingsItem<?>> items;
/**
* Settings are stored in this file.
*/
private static final File settingsFile = new File(ClientConfig.getInstance().getHomeDirectory(), "settings.ser");
/**
* Singleton instance of this class.
*/
private static Settings settings = new Settings();
/**
* The way to instantiate the settings. Is set to private to deny other
* instances of that object.
*
* @since Envoy Client v0.2-alpha
*/
private Settings() {
// Load settings from settings file
try {
items = SerializationUtils.read(settingsFile, HashMap.class);
} catch (ClassNotFoundException | IOException e) {
items = new HashMap<>();
}
supplementDefaults();
}
/**
* This method is used to ensure that there is only one instance of Settings.
*
* @return the instance of Settings
* @since Envoy Client v0.2-alpha
*/
public static Settings getInstance() { return settings; }
/**
* Updates the preferences when the save button is clicked.
*
* @throws IOException if an error occurs while saving the themes
* @since Envoy Client v0.2-alpha
*/
public void save() throws IOException {
// Save settings to settings file
SerializationUtils.write(settingsFile, items);
}
private void supplementDefaults() {
items.putIfAbsent("enterToSend", new SettingsItem<>(true, "Enter to send", "Sends a message by pressing the enter key."));
items.putIfAbsent("onCloseMode", new SettingsItem<>(true, "Hide on close", "Hides the chat window when it is closed."));
items.putIfAbsent("currentTheme", new SettingsItem<>("dark", "Current Theme Name", "The name of the currently selected theme."));
}
/**
* @return the name of the currently active theme
* @since Envoy Client v0.2-alpha
*/
public String getCurrentTheme() { return (String) items.get("currentTheme").get(); }
/**
* Sets the name of the current theme.
*
* @param themeName the name to set
* @since Envoy Client v0.2-alpha
*/
public void setCurrentTheme(String themeName) { ((SettingsItem<String>) items.get("currentTheme")).set(themeName); }
/**
* @return true if the currently used theme is one of the default themes
* @since Envoy Client v0.1-beta
*/
public boolean isUsingDefaultTheme() {
final var theme = getCurrentTheme();
return theme.equals("dark") || theme.equals("light");
}
/**
* @return {@code true}, if pressing the {@code Enter} key suffices to send a
* message. Otherwise it has to be pressed in conjunction with the
* {@code Control} key.
* @since Envoy Client v0.2-alpha
*/
public Boolean isEnterToSend() { return (Boolean) items.get("enterToSend").get(); }
/**
* Changes the keystrokes performed by the user to send a message.
*
* @param enterToSend If set to {@code true} a message can be sent by pressing
* the {@code Enter} key. Otherwise it has to be pressed in
* conjunction with the {@code Control} key.
* @since Envoy Client v0.2-alpha
*/
public void setEnterToSend(boolean enterToSend) { ((SettingsItem<Boolean>) items.get("enterToSend")).set(enterToSend); }
/**
* @return the current on close mode.
* @since Envoy Client v0.3-alpha
*/
public Boolean getCurrentOnCloseMode() { return (Boolean) items.get("onCloseMode").get(); }
/**
* Sets the current on close mode.
*
* @param currentOnCloseMode the on close mode that should be set.
* @since Envoy Client v0.3-alpha
*/
public void setCurrentOnCloseMode(boolean currentOnCloseMode) { ((SettingsItem<Boolean>) items.get("onCloseMode")).set(currentOnCloseMode); }
/**
* @return the items
*/
public Map<String, SettingsItem<?>> getItems() { return items; }
/**
* @param items the items to set
*/
public void setItems(Map<String, SettingsItem<?>> items) { this.items = items; }
}

View File

@ -0,0 +1,99 @@
package envoy.client.data;
import java.io.Serializable;
import java.util.function.Consumer;
import javax.swing.JComponent;
/**
* Encapsulates a persistent value that is directly or indirectly mutable by the
* user.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>SettingsItem.java</strong><br>
* Created: <strong>23.12.2019</strong><br>
*
* @param <T> the type of this {@link SettingsItem}'s value
* @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha
*/
public class SettingsItem<T> implements Serializable {
private T value;
private String userFriendlyName, description;
private transient Consumer<T> changeHandler;
private static final long serialVersionUID = 1L;
/**
* Initializes a {@link SettingsItem}. The default value's class will be mapped
* to a {@link JComponent} that can be used to display this {@link SettingsItem}
* to the user.
*
* @param value the default value
* @param userFriendlyName the user friendly name (short)
* @param description the description (long)
* @since Envoy Client v0.3-alpha
*/
public SettingsItem(T value, String userFriendlyName, String description) {
this.value = value;
this.userFriendlyName = userFriendlyName;
this.description = description;
}
/**
* @return the value
* @since Envoy Client v0.3-alpha
*/
public T get() { return value; }
/**
* Changes the value of this {@link SettingsItem}. If a {@code ChangeHandler} if
* defined, it will be invoked with this value.
*
* @param value the value to set
* @since Envoy Client v0.3-alpha
*/
public void set(T value) {
if (changeHandler != null && value != this.value) changeHandler.accept(value);
this.value = value;
}
/**
* @return the userFriendlyName
* @since Envoy Client v0.3-alpha
*/
public String getUserFriendlyName() { return userFriendlyName; }
/**
* @param userFriendlyName the userFriendlyName to set
* @since Envoy Client v0.3-alpha
*/
public void setUserFriendlyName(String userFriendlyName) { this.userFriendlyName = userFriendlyName; }
/**
* @return the description
* @since Envoy Client v0.3-alpha
*/
public String getDescription() { return description; }
/**
* @param description the description to set
* @since Envoy Client v0.3-alpha
*/
public void setDescription(String description) { this.description = description; }
/**
* Sets a {@code ChangeHandler} for this {@link SettingsItem}. It will be
* invoked with the current value once during the registration and every time
* when the value changes.
*
* @param changeHandler the changeHandler to set
* @since Envoy Client v0.3-alpha
*/
public void setChangeHandler(Consumer<T> changeHandler) {
this.changeHandler = changeHandler;
changeHandler.accept(value);
}
}

View File

@ -0,0 +1,15 @@
package envoy.client.data;
/**
* Implements a {@link LocalDB} in a way that does not persist any information
* after application shutdown.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>TransientLocalDB.java</strong><br>
* Created: <strong>3 Feb 2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha
*/
public final class TransientLocalDB extends LocalDB {
}

View File

@ -0,0 +1,64 @@
package envoy.client.data.audio;
import javax.sound.sampled.*;
import envoy.exception.EnvoyException;
/**
* Plays back audio from a byte array.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>AudioPlayer.java</strong><br>
* Created: <strong>05.07.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public final class AudioPlayer {
private final AudioFormat format;
private final DataLine.Info info;
private Clip clip;
/**
* Initializes the player with the default audio format.
*
* @since Envoy Client v0.1-beta
*/
public AudioPlayer() { this(AudioRecorder.DEFAULT_AUDIO_FORMAT); }
/**
* Initializes the player with a given audio format.
*
* @param format the audio format to use
* @since Envoy Client v0.1-beta
*/
public AudioPlayer(AudioFormat format) {
this.format = format;
info = new DataLine.Info(Clip.class, format);
}
/**
* @return {@code true} if audio play back is supported
* @since Envoy Client v0.1-beta
*/
public boolean isSupported() { return AudioSystem.isLineSupported(info); }
/**
* Plays back an audio clip.
*
* @param data the data of the clip
* @throws EnvoyException if the play back failed
* @since Envoy Client v0.1-beta
*/
public void play(byte[] data) throws EnvoyException {
try {
clip = (Clip) AudioSystem.getLine(info);
clip.open(format, data, 0, data.length);
clip.start();
} catch (final LineUnavailableException e) {
throw new EnvoyException("Cannot play back audio", e);
}
}
}

View File

@ -0,0 +1,122 @@
package envoy.client.data.audio;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import javax.sound.sampled.*;
import envoy.exception.EnvoyException;
/**
* Records audio and exports it as a byte array.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>AudioRecorder.java</strong><br>
* Created: <strong>02.07.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public final class AudioRecorder {
/**
* The default audio format used for recording and play back.
*
* @since Envoy Client v0.1-beta
*/
public static final AudioFormat DEFAULT_AUDIO_FORMAT = new AudioFormat(16000, 16, 1, true, false);
private final AudioFormat format;
private final DataLine.Info info;
private TargetDataLine line;
private Path tempFile;
/**
* Initializes the recorder with the default audio format.
*
* @since Envoy Client v0.1-beta
*/
public AudioRecorder() { this(DEFAULT_AUDIO_FORMAT); }
/**
* Initializes the recorder with a given audio format.
*
* @param format the audio format to use
* @since Envoy Client v0.1-beta
*/
public AudioRecorder(AudioFormat format) {
this.format = format;
info = new DataLine.Info(TargetDataLine.class, format);
}
/**
* @return {@code true} if audio recording is supported
* @since Envoy Client v0.1-beta
*/
public boolean isSupported() { return AudioSystem.isLineSupported(info); }
/**
* @return {@code true} if the recorder is active
* @since Envoy Client v0.1-beta
*/
public boolean isRecording() { return line != null && line.isActive(); }
/**
* Starts the audio recording.
*
* @throws EnvoyException if starting the recording failed
* @since Envoy Client v0.1-beta
*/
public void start() throws EnvoyException {
try {
// Open the line
line = (TargetDataLine) AudioSystem.getLine(info);
line.open(format);
line.start();
// Prepare temp file
tempFile = Files.createTempFile("recording", "wav");
// Start the recording
final var ais = new AudioInputStream(line);
AudioSystem.write(ais, AudioFileFormat.Type.WAVE, tempFile.toFile());
} catch (IOException | LineUnavailableException e) {
throw new EnvoyException("Cannot record voice", e);
}
}
/**
* Stops the recording.
*
* @return the finished recording
* @throws EnvoyException if finishing the recording failed
* @since Envoy Client v0.1-beta
*/
public byte[] finish() throws EnvoyException {
try {
line.stop();
line.close();
final byte[] data = Files.readAllBytes(tempFile);
Files.delete(tempFile);
return data;
} catch (final IOException e) {
throw new EnvoyException("Cannot save voice recording", e);
}
}
/**
* Cancels the active recording.
*
* @since Envoy Client v0.1-beta
*/
public void cancel() {
line.stop();
line.close();
try {
Files.deleteIfExists(tempFile);
} catch (IOException e) {}
}
}

View File

@ -0,0 +1,11 @@
/**
* Contains classes related to recording and playing back audio clips.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>05.07.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
package envoy.client.data.audio;

View File

@ -0,0 +1,9 @@
/**
* This package contains all data classes and classes related to persistence.
*
* @author Kai S. K. Engelbart
* @author Leon Hofmeister
* @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta
*/
package envoy.client.data;