2020-02-04 19:46:18 +01:00
|
|
|
package envoy.client.net;
|
2019-10-06 10:45:19 +02:00
|
|
|
|
2019-12-31 10:57:11 +01:00
|
|
|
import java.io.Closeable;
|
|
|
|
import java.io.IOException;
|
2019-12-29 11:54:05 +01:00
|
|
|
import java.net.Socket;
|
2019-12-31 15:38:52 +01:00
|
|
|
import java.util.HashMap;
|
2019-12-14 10:53:20 +01:00
|
|
|
import java.util.Map;
|
2020-04-02 22:03:43 +02:00
|
|
|
import java.util.Set;
|
2020-04-10 21:57:05 +02:00
|
|
|
import java.util.concurrent.TimeoutException;
|
2019-12-21 18:29:59 +01:00
|
|
|
import java.util.logging.Logger;
|
2019-11-27 17:07:25 +01:00
|
|
|
|
2020-02-06 18:35:05 +01:00
|
|
|
import envoy.client.data.Cache;
|
2020-03-05 14:27:40 +01:00
|
|
|
import envoy.client.data.ClientConfig;
|
2020-03-24 18:38:47 +01:00
|
|
|
import envoy.client.data.LocalDB;
|
2020-02-11 18:15:15 +01:00
|
|
|
import envoy.client.event.SendEvent;
|
2020-01-02 16:11:41 +01:00
|
|
|
import envoy.data.*;
|
2020-02-07 15:27:26 +01:00
|
|
|
import envoy.event.*;
|
2020-04-02 22:03:43 +02:00
|
|
|
import envoy.event.contact.ContactOperationEvent;
|
|
|
|
import envoy.event.contact.ContactSearchResult;
|
2020-03-05 14:27:40 +01:00
|
|
|
import envoy.util.EnvoyLog;
|
2019-12-29 11:54:05 +01:00
|
|
|
import envoy.util.SerializationUtils;
|
2019-10-06 10:45:19 +02:00
|
|
|
|
|
|
|
/**
|
2020-02-04 19:46:18 +01:00
|
|
|
* Establishes a connection to the server, performs a handshake and delivers
|
|
|
|
* certain objects to the server.<br>
|
|
|
|
* <br>
|
2019-10-06 10:45:19 +02:00
|
|
|
* Project: <strong>envoy-client</strong><br>
|
2019-10-12 11:19:29 +02:00
|
|
|
* File: <strong>Client.java</strong><br>
|
2019-10-06 10:45:19 +02:00
|
|
|
* Created: <strong>28 Sep 2019</strong><br>
|
2019-12-21 18:29:59 +01:00
|
|
|
*
|
2019-10-12 08:19:00 +02:00
|
|
|
* @author Kai S. K. Engelbart
|
|
|
|
* @author Maximilian Käfer
|
2019-10-12 14:45:58 +02:00
|
|
|
* @author Leon Hofmeister
|
2020-03-23 21:52:33 +01:00
|
|
|
* @since Envoy Client v0.1-alpha
|
2019-10-06 10:45:19 +02:00
|
|
|
*/
|
2019-12-31 10:57:11 +01:00
|
|
|
public class Client implements Closeable {
|
|
|
|
|
2020-02-04 19:46:18 +01:00
|
|
|
// Connection handling
|
2019-12-31 10:57:11 +01:00
|
|
|
private Socket socket;
|
|
|
|
private Receiver receiver;
|
|
|
|
private boolean online;
|
2019-10-06 10:45:19 +02:00
|
|
|
|
2020-02-04 19:46:18 +01:00
|
|
|
// Asynchronously initialized during handshake
|
2020-04-02 22:03:43 +02:00
|
|
|
private volatile User sender;
|
|
|
|
private volatile Set<? extends Contact> contacts;
|
|
|
|
private volatile boolean rejected;
|
2019-10-06 10:45:19 +02:00
|
|
|
|
2020-04-10 11:01:03 +02:00
|
|
|
// Configuration, logging and event management
|
|
|
|
private static final ClientConfig config = ClientConfig.getInstance();
|
|
|
|
private static final Logger logger = EnvoyLog.getLogger(Client.class);
|
|
|
|
private static final EventBus eventBus = EventBus.getInstance();
|
2019-12-21 18:29:59 +01:00
|
|
|
|
2019-12-14 11:30:00 +01:00
|
|
|
/**
|
2020-01-02 16:11:41 +01:00
|
|
|
* Enters the online mode by acquiring a user ID from the server. As a
|
|
|
|
* connection has to be established and a handshake has to be made, this method
|
|
|
|
* will block for up to 5 seconds. If the handshake does exceed this time limit,
|
|
|
|
* an exception is thrown.
|
2019-12-21 18:29:59 +01:00
|
|
|
*
|
2020-02-08 11:43:02 +01:00
|
|
|
* @param credentials the login credentials of the user
|
|
|
|
* @param receivedMessageCache a message cache containing all unread messages
|
|
|
|
* from the server that can be relayed after
|
|
|
|
* initialization
|
2020-04-10 21:57:05 +02:00
|
|
|
* @throws TimeoutException if the server could not be reached
|
|
|
|
* @throws IOException if the login credentials could not be
|
|
|
|
* written
|
|
|
|
* @throws InterruptedException if the current thread is interrupted while
|
|
|
|
* waiting for the handshake response
|
2019-12-14 11:30:00 +01:00
|
|
|
*/
|
2020-02-12 07:53:24 +01:00
|
|
|
public void performHandshake(LoginCredentials credentials, Cache<Message> receivedMessageCache)
|
2020-04-10 21:57:05 +02:00
|
|
|
throws TimeoutException, IOException, InterruptedException {
|
2020-02-12 07:53:24 +01:00
|
|
|
if (online) throw new IllegalStateException("Handshake has already been performed successfully");
|
2019-12-31 10:57:11 +01:00
|
|
|
// Establish TCP connection
|
2020-04-06 22:55:59 +02:00
|
|
|
logger.finer(String.format("Attempting connection to server %s:%d...", config.getServer(), config.getPort()));
|
2019-12-29 11:54:05 +01:00
|
|
|
socket = new Socket(config.getServer(), config.getPort());
|
2020-04-06 22:55:59 +02:00
|
|
|
logger.fine("Successfully established TCP connection to server");
|
2019-12-29 11:54:05 +01:00
|
|
|
|
2019-12-30 17:18:03 +01:00
|
|
|
// Create message receiver
|
2019-12-31 10:57:11 +01:00
|
|
|
receiver = new Receiver(socket.getInputStream());
|
2019-11-04 23:10:53 +01:00
|
|
|
|
2020-02-04 19:13:31 +01:00
|
|
|
// Register user creation processor, contact list processor and message cache
|
2020-04-02 22:03:43 +02:00
|
|
|
receiver.registerProcessor(User.class, sender -> { this.sender = sender; contacts = sender.getContacts(); });
|
2020-02-08 11:43:02 +01:00
|
|
|
receiver.registerProcessor(Message.class, receivedMessageCache);
|
2020-04-10 11:01:03 +02:00
|
|
|
receiver.registerProcessor(HandshakeRejectionEvent.class, evt -> { rejected = true; eventBus.dispatch(evt); });
|
2020-02-12 07:53:24 +01:00
|
|
|
|
|
|
|
rejected = false;
|
2019-12-30 17:18:03 +01:00
|
|
|
|
2019-12-31 15:38:52 +01:00
|
|
|
// Start receiver
|
2020-03-07 19:38:06 +01:00
|
|
|
receiver.start();
|
2019-12-31 15:38:52 +01:00
|
|
|
|
2019-12-31 10:57:11 +01:00
|
|
|
// Write login credentials
|
|
|
|
SerializationUtils.writeBytesWithLength(credentials, socket.getOutputStream());
|
|
|
|
|
2019-12-30 17:18:03 +01:00
|
|
|
// Wait for a maximum of five seconds to acquire the sender object
|
|
|
|
long start = System.currentTimeMillis();
|
2020-04-02 22:03:43 +02:00
|
|
|
while (sender == null) {
|
2020-02-12 07:53:24 +01:00
|
|
|
|
|
|
|
// Quit immediately after handshake rejection
|
|
|
|
// This method can then be called again
|
|
|
|
if (rejected) {
|
|
|
|
socket.close();
|
|
|
|
receiver.removeAllProcessors();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-04-10 21:57:05 +02:00
|
|
|
if (System.currentTimeMillis() - start > 5000) throw new TimeoutException("Did not log in after 5 seconds");
|
2019-12-30 17:18:03 +01:00
|
|
|
Thread.sleep(500);
|
2019-12-29 11:54:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
online = true;
|
2020-01-02 16:11:41 +01:00
|
|
|
|
|
|
|
// Remove user creation processor
|
|
|
|
receiver.removeAllProcessors();
|
2020-04-10 16:18:01 +02:00
|
|
|
|
|
|
|
logger.info("Handshake completed.");
|
2020-02-12 07:53:24 +01:00
|
|
|
}
|
2020-01-02 16:11:41 +01:00
|
|
|
|
2020-02-12 07:53:24 +01:00
|
|
|
/**
|
|
|
|
* Initializes the {@link Receiver} used to process data sent from the server to
|
|
|
|
* this client.
|
|
|
|
*
|
2020-03-24 18:38:47 +01:00
|
|
|
* @param localDB the local database used to persist the current
|
2020-03-26 16:06:18 +01:00
|
|
|
* {@link IDGenerator}
|
2020-02-12 07:53:24 +01:00
|
|
|
* @param receivedMessageCache a message cache containing all unread messages
|
|
|
|
* from the server that can be relayed after
|
|
|
|
* initialization
|
2020-03-26 16:06:18 +01:00
|
|
|
* @throws IOException if no {@link IDGenerator} is present and none could be
|
2020-02-12 07:53:24 +01:00
|
|
|
* requested from the server
|
2020-03-23 21:52:33 +01:00
|
|
|
* @since Envoy Client v0.2-alpha
|
2020-02-12 07:53:24 +01:00
|
|
|
*/
|
2020-03-24 18:38:47 +01:00
|
|
|
public void initReceiver(LocalDB localDB, Cache<Message> receivedMessageCache) throws IOException {
|
2020-02-12 07:53:24 +01:00
|
|
|
checkOnline();
|
2020-02-07 15:27:26 +01:00
|
|
|
|
|
|
|
// Process incoming messages
|
2020-02-04 19:13:31 +01:00
|
|
|
final ReceivedMessageProcessor receivedMessageProcessor = new ReceivedMessageProcessor();
|
|
|
|
receiver.registerProcessor(Message.class, receivedMessageProcessor);
|
|
|
|
|
|
|
|
// Relay cached unread messages
|
2020-02-08 11:43:02 +01:00
|
|
|
receivedMessageCache.setProcessor(receivedMessageProcessor);
|
2020-02-04 19:13:31 +01:00
|
|
|
|
2020-02-05 07:09:25 +01:00
|
|
|
// Process message status changes
|
|
|
|
receiver.registerProcessor(MessageStatusChangeEvent.class, new MessageStatusChangeEventProcessor());
|
2020-01-29 07:44:25 +01:00
|
|
|
|
2020-02-07 15:27:26 +01:00
|
|
|
// Process user status changes
|
2020-03-24 18:38:47 +01:00
|
|
|
receiver.registerProcessor(UserStatusChangeEvent.class, new UserStatusChangeProcessor(localDB));
|
2020-02-07 15:27:26 +01:00
|
|
|
|
2020-01-29 07:44:25 +01:00
|
|
|
// Process message ID generation
|
2020-03-26 16:06:18 +01:00
|
|
|
receiver.registerProcessor(IDGenerator.class, localDB::setIDGenerator);
|
2020-01-29 07:44:25 +01:00
|
|
|
|
2020-04-02 09:23:47 +02:00
|
|
|
// Process name changes
|
2020-04-10 11:01:03 +02:00
|
|
|
receiver.registerProcessor(NameChangeEvent.class, evt -> { localDB.replaceContactName(evt); eventBus.dispatch(evt); });
|
2020-04-02 09:23:47 +02:00
|
|
|
|
2020-02-09 16:26:36 +01:00
|
|
|
// Process contact searches
|
2020-04-10 11:01:03 +02:00
|
|
|
receiver.registerProcessor(ContactSearchResult.class, eventBus::dispatch);
|
2020-02-09 16:26:36 +01:00
|
|
|
|
2020-04-02 22:03:43 +02:00
|
|
|
receiver.registerProcessor(Contact.class,
|
2020-04-10 11:01:03 +02:00
|
|
|
contacts -> eventBus.dispatch(new ContactOperationEvent(contacts.getContacts().iterator().next(), ElementOperation.ADD)));
|
2020-02-11 18:15:15 +01:00
|
|
|
|
2020-04-02 09:23:47 +02:00
|
|
|
// Process group size changes
|
2020-04-10 11:01:03 +02:00
|
|
|
receiver.registerProcessor(GroupResizeEvent.class, evt -> { localDB.updateGroup(evt); eventBus.dispatch(evt); });
|
2020-04-02 09:23:47 +02:00
|
|
|
|
2020-02-11 18:15:15 +01:00
|
|
|
// Send event
|
2020-04-10 11:01:03 +02:00
|
|
|
eventBus.register(SendEvent.class, evt -> {
|
2020-02-11 18:15:15 +01:00
|
|
|
try {
|
2020-02-12 06:12:04 +01:00
|
|
|
sendEvent(evt.get());
|
2020-02-11 18:15:15 +01:00
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-02-05 07:09:25 +01:00
|
|
|
// Request a generator if none is present or the existing one is consumed
|
2020-03-26 16:06:18 +01:00
|
|
|
if (!localDB.hasIDGenerator() || !localDB.getIDGenerator().hasNext()) requestIdGenerator();
|
2019-11-04 23:10:53 +01:00
|
|
|
}
|
2019-11-09 13:25:18 +01:00
|
|
|
|
2020-02-06 21:03:08 +01:00
|
|
|
/**
|
|
|
|
* Creates a new write proxy that uses this client to communicate with the
|
|
|
|
* server.
|
|
|
|
*
|
2020-03-24 18:38:47 +01:00
|
|
|
* @param localDB the local database that the write proxy will use to access
|
2020-02-06 21:03:08 +01:00
|
|
|
* caches
|
|
|
|
* @return a new write proxy
|
|
|
|
* @since Envoy Client v0.3-alpha
|
|
|
|
*/
|
2020-03-24 18:38:47 +01:00
|
|
|
public WriteProxy createWriteProxy(LocalDB localDB) { return new WriteProxy(this, localDB); }
|
2020-02-06 21:03:08 +01:00
|
|
|
|
2019-12-31 10:57:11 +01:00
|
|
|
/**
|
2020-02-05 07:09:25 +01:00
|
|
|
* Sends a message to the server. The message's status will be incremented once
|
|
|
|
* it was delivered successfully.
|
2020-01-29 07:44:25 +01:00
|
|
|
*
|
2019-12-31 10:57:11 +01:00
|
|
|
* @param message the message to send
|
|
|
|
* @throws IOException if the message does not reach the server
|
2020-03-23 21:52:33 +01:00
|
|
|
* @since Envoy Client v0.3-alpha
|
2019-12-31 10:57:11 +01:00
|
|
|
*/
|
|
|
|
public void sendMessage(Message message) throws IOException {
|
2020-01-29 07:44:25 +01:00
|
|
|
writeObject(message);
|
2020-01-02 16:11:41 +01:00
|
|
|
message.nextStatus();
|
2019-12-31 10:57:11 +01:00
|
|
|
}
|
|
|
|
|
2020-02-05 07:09:25 +01:00
|
|
|
/**
|
|
|
|
* Sends an event to the server.
|
|
|
|
*
|
|
|
|
* @param evt the event to send
|
|
|
|
* @throws IOException if the event did not reach the server
|
|
|
|
*/
|
|
|
|
public void sendEvent(Event<?> evt) throws IOException { writeObject(evt); }
|
|
|
|
|
2020-01-29 07:44:25 +01:00
|
|
|
/**
|
2020-03-26 16:06:18 +01:00
|
|
|
* Requests a new {@link IDGenerator} from the server.
|
2020-01-29 07:44:25 +01:00
|
|
|
*
|
|
|
|
* @throws IOException if the request does not reach the server
|
2020-03-23 21:52:33 +01:00
|
|
|
* @since Envoy Client v0.3-alpha
|
2020-01-29 07:44:25 +01:00
|
|
|
*/
|
|
|
|
public void requestIdGenerator() throws IOException {
|
|
|
|
logger.info("Requesting new id generator...");
|
2020-03-26 16:06:18 +01:00
|
|
|
writeObject(new IDGeneratorRequest());
|
2020-01-29 07:44:25 +01:00
|
|
|
}
|
|
|
|
|
2019-11-04 23:10:53 +01:00
|
|
|
/**
|
2019-12-14 11:30:00 +01:00
|
|
|
* @return a {@code Map<String, User>} of all users on the server with their
|
|
|
|
* user names as keys
|
2020-03-23 21:52:33 +01:00
|
|
|
* @since Envoy Client v0.2-alpha
|
2019-11-04 23:10:53 +01:00
|
|
|
*/
|
2020-04-02 22:03:43 +02:00
|
|
|
public Map<String, Contact> getUsers() {
|
2019-12-31 15:38:52 +01:00
|
|
|
checkOnline();
|
2020-04-02 22:03:43 +02:00
|
|
|
Map<String, Contact> users = new HashMap<>();
|
|
|
|
contacts.forEach(u -> users.put(u.getName(), u));
|
2020-06-07 16:26:54 +02:00
|
|
|
users.put(sender.getName(), sender);
|
2019-12-31 15:38:52 +01:00
|
|
|
return users;
|
2019-10-19 15:09:32 +02:00
|
|
|
}
|
|
|
|
|
2019-12-31 10:57:11 +01:00
|
|
|
@Override
|
|
|
|
public void close() throws IOException { if (online) socket.close(); }
|
|
|
|
|
2020-01-29 07:44:25 +01:00
|
|
|
private void writeObject(Object obj) throws IOException {
|
|
|
|
checkOnline();
|
2020-04-18 10:34:03 +02:00
|
|
|
logger.fine("Sending " + obj);
|
2020-01-29 07:44:25 +01:00
|
|
|
SerializationUtils.writeBytesWithLength(obj, socket.getOutputStream());
|
|
|
|
}
|
|
|
|
|
2019-12-31 15:38:52 +01:00
|
|
|
private void checkOnline() { if (!online) throw new IllegalStateException("Client is not online"); }
|
|
|
|
|
2019-10-12 17:35:58 +02:00
|
|
|
/**
|
2019-11-04 23:10:53 +01:00
|
|
|
* @return the sender object that represents this client.
|
2020-03-23 21:52:33 +01:00
|
|
|
* @since Envoy Client v0.1-alpha
|
2019-10-12 17:35:58 +02:00
|
|
|
*/
|
2019-10-19 12:10:52 +02:00
|
|
|
public User getSender() { return sender; }
|
|
|
|
|
2019-12-14 11:30:00 +01:00
|
|
|
/**
|
|
|
|
* Sets the client user which is used to send messages.
|
2019-12-21 18:29:59 +01:00
|
|
|
*
|
2020-04-02 22:03:43 +02:00
|
|
|
* @param clientUser the client user to set
|
2020-03-23 21:52:33 +01:00
|
|
|
* @since Envoy Client v0.2-alpha
|
2019-12-14 11:30:00 +01:00
|
|
|
*/
|
2020-04-02 22:03:43 +02:00
|
|
|
public void setSender(User clientUser) { this.sender = clientUser; }
|
2019-12-14 10:53:20 +01:00
|
|
|
|
2019-12-31 10:57:11 +01:00
|
|
|
/**
|
|
|
|
* @return the {@link Receiver} used by this {@link Client}
|
|
|
|
*/
|
|
|
|
public Receiver getReceiver() { return receiver; }
|
|
|
|
|
2019-12-11 18:52:30 +01:00
|
|
|
/**
|
|
|
|
* @return {@code true} if a connection to the server could be established
|
2020-03-23 21:52:33 +01:00
|
|
|
* @since Envoy Client v0.2-alpha
|
2019-12-11 18:52:30 +01:00
|
|
|
*/
|
|
|
|
public boolean isOnline() { return online; }
|
2020-02-07 15:27:26 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @return the contacts of this {@link Client}
|
2020-03-23 21:52:33 +01:00
|
|
|
* @since Envoy Client v0.3-alpha
|
2020-02-07 15:27:26 +01:00
|
|
|
*/
|
2020-04-02 22:03:43 +02:00
|
|
|
public Set<? extends Contact> getContacts() { return contacts; }
|
2020-02-07 15:27:26 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param contacts the contacts to set
|
2020-03-23 21:52:33 +01:00
|
|
|
* @since Envoy Client v0.3-alpha
|
2020-02-07 15:27:26 +01:00
|
|
|
*/
|
2020-04-02 22:03:43 +02:00
|
|
|
public void setContacts(Set<? extends Contact> contacts) { this.contacts = contacts; }
|
2020-02-07 15:41:17 +01:00
|
|
|
}
|