diff --git a/src/main/java/dslab/Message.java b/src/main/java/dslab/Message.java index cf99d2e..b5bdffd 100644 --- a/src/main/java/dslab/Message.java +++ b/src/main/java/dslab/Message.java @@ -9,8 +9,8 @@ import java.util.stream.Collectors; public class Message { private ArrayList to = new ArrayList<>(); private Email from; - private String subject; - private String data; + private String subject = ""; + private String data = ""; private Integer id; public Message() { @@ -80,6 +80,10 @@ public class Message { this.id = id; } + public String listMessage() { + return getId() + " " + getFrom() + " " + getSubject(); + } + @Override public String toString() { return "from " + getFrom().toString() + "\n" + diff --git a/src/main/java/dslab/exception/MessageNotFoundException.java b/src/main/java/dslab/exception/MessageNotFoundException.java new file mode 100644 index 0000000..860d6ff --- /dev/null +++ b/src/main/java/dslab/exception/MessageNotFoundException.java @@ -0,0 +1,7 @@ +package dslab.exception; + +public class MessageNotFoundException extends Exception { + public MessageNotFoundException(String errorMessage) { + super(errorMessage); + } +} diff --git a/src/main/java/dslab/mailbox/DMAPConnection.java b/src/main/java/dslab/mailbox/DMAPConnection.java index 936ad3e..d135aa1 100644 --- a/src/main/java/dslab/mailbox/DMAPConnection.java +++ b/src/main/java/dslab/mailbox/DMAPConnection.java @@ -1,4 +1,187 @@ package dslab.mailbox; -public class DMAPConnection { +import dslab.Email; +import dslab.Message; +import dslab.exception.MessageNotFoundException; + +import java.io.*; +import java.net.Socket; +import java.net.SocketException; +import java.util.LinkedList; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +public class DMAPConnection implements Runnable { + Logger logger = Logger.getLogger(DMAPConnection.class.getName()); + private final Socket socket; + private PrintWriter out; + private BufferedReader in; + private Email currentUser = null; + private final ConcurrentHashMap> storage; + private final ConcurrentHashMap userStorage; + + public DMAPConnection(Socket connection, ConcurrentHashMap> storage, ConcurrentHashMap userStorage) { + this.socket = connection; + this.storage = storage; + this.userStorage = userStorage; + } + + @Override + public void run() { + logger.finer("Preparing for DMAP communication in " + this.toString()); + try { + this.out = new PrintWriter(socket.getOutputStream(), true); + this.in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + out.println("ok DMAP"); + loginLoop(); + out.println("ok"); + + String userInput; + while (!Thread.currentThread().isInterrupted() && (userInput = in.readLine()) != null) { + if ("quit".equals(userInput)) { + out.println("ok bye"); + shutdown(); + } else if ("logout".equals(userInput)) { + out.println("ok"); + currentUser = null; + loginLoop(); + } else if ("list".equals(userInput)) { + listMessages(); + } else if ("delete".equals(userInput.split("\\s+")[0])) { + if (userInput.split("\\s+").length == 2) { + deleteMessage(userInput.split("\\s+")[1]); + } else { + out.println("Please supply a message id to delete!"); + } + } else if ("show".equals(userInput.split("\\s+")[0])) { + if (userInput.split("\\s+").length == 2) { + showMessage(userInput.split("\\s+")[1]); + } else { + out.println("Please supply a message id to show!"); + } + } else { + out.println("error protocol error"); + shutdown(); + } + } + } catch (InterruptedIOException ioe) { + logger.info("Received interrupt from parent. Shutting down..."); + shutdown(); + } catch (IOException e) { + logger.severe("Failed to get IO-Stream"); + e.printStackTrace(); + shutdown(); + } catch (MessageNotFoundException e) { + out.println(e.getMessage()); + } + } + + private void loginLoop() { + String userInput; + + try { + while (!Thread.currentThread().isInterrupted()) { + userInput = in.readLine(); + if ("quit".equals(userInput.split("\\s+")[0])) { + out.println("ok bye"); + shutdown(); + } else if ("login".equals(userInput.split("\\s+")[0])) { + String[] args = userInput.split("\\s+"); + if (args.length != 3) + out.println("Please specify a username and password to login!"); + if (this.userStorage.containsKey(args[1])) { + // Check if username exists + if (args[2].equals(this.userStorage.get(args[1]))) { + // Check if password matches + for (Email email : this.storage.keySet()) { + if (args[1].equals(email.getUsername())) { + // Set current user if login successful + currentUser = email; + logger.info("User successfully logged in: " + currentUser.toString()); + return; + } + } + } else { + out.println("error wrong password"); + } + } else { + out.println("error unknown user"); + } + } else { + out.println("error not logged in"); + } + } + } catch (InterruptedIOException ioe) { + logger.info("Received interrupt from parent. Shutting down..."); + shutdown(); + } catch (SocketException e) { + logger.finer("Received interrupt. Exiting " + this.toString()); + shutdown(); + } catch (IOException e) { + logger.severe("Failed to get IO-Stream"); + e.printStackTrace(); + shutdown(); + } + } + + private void showMessage(String id) throws MessageNotFoundException { + int i; + try { + i = Integer.parseInt(id); + } catch (NumberFormatException e) { + throw new MessageNotFoundException("error unknown message id"); + } + + for (Message m : storage.get(currentUser)) { + if (m.getId() == i) { + out.println(m); + return; + } + } + + throw new MessageNotFoundException("error unknown message id"); + } + + private void listMessages() { + if (storage.get(currentUser).isEmpty()) { + out.println("You do not have any messages at the moment!"); + return; + } + + for (Message m : storage.get(currentUser)) { + out.println(m.listMessage()); + } + } + + public void deleteMessage (String id) throws MessageNotFoundException { + int i; + try { + i = Integer.parseInt(id); + } catch (NumberFormatException e) { + throw new MessageNotFoundException("error unknown message id"); + } + + for (Message m : storage.get(currentUser)) { + if (m.getId() == i) { + storage.get(currentUser).remove(m); + return; + } + } + + throw new MessageNotFoundException("error unknown message id"); + } + + public void shutdown() { + logger.info("Shutting down client connection " + this.toString()); + try { + if (socket != null) + socket.close(); + in.close(); + out.close(); + } catch (IOException e) { + logger.severe("Error closing socket and/or IO-streams"); + e.printStackTrace(); + } + Thread.currentThread().interrupt(); + } } diff --git a/src/main/java/dslab/mailbox/DMAPListener.java b/src/main/java/dslab/mailbox/DMAPListener.java index 47cb298..10b5d1f 100644 --- a/src/main/java/dslab/mailbox/DMAPListener.java +++ b/src/main/java/dslab/mailbox/DMAPListener.java @@ -1,12 +1,76 @@ package dslab.mailbox; +import dslab.Email; import dslab.Message; import dslab.transfer.ClientConnection; import java.io.BufferedReader; +import java.io.IOException; +import java.io.InterruptedIOException; import java.io.PrintWriter; +import java.net.ServerSocket; import java.net.Socket; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.logging.Logger; -public class DMAPListener { +public class DMAPListener extends Thread { + private final ServerSocket serverSocket; + private final Logger logger = Logger.getLogger(DMAPListener.class.getName()); + private final ConcurrentHashMap> storage; + private final ConcurrentHashMap userStorage; + private final ArrayList clients = new ArrayList<>(); + private final ExecutorService executorService = Executors.newCachedThreadPool(); + + public DMAPListener(ServerSocket serverSocket, ConcurrentHashMap> storage, ConcurrentHashMap userStorage) { + this.serverSocket = serverSocket; + this.storage = storage; + this.userStorage = userStorage; + } + + @Override + public void run() { + while (!Thread.currentThread().isInterrupted()) { + logger.finer("Waiting for request on serverSocket " + serverSocket.toString()); + try { + Socket s = serverSocket.accept(); + logger.fine("Processing incoming socket " + s.toString()); + DMAPConnection dmapConnection = new DMAPConnection(s, storage, userStorage); + clients.add(dmapConnection); + executorService.submit(dmapConnection); + } catch (InterruptedIOException | SocketException e) { + logger.finer("Received interrupt. Exiting " + this.toString()); + this.shutdown(); + } catch (IOException e) { + logger.severe("Error starting serverSocket " + serverSocket.toString()); + e.printStackTrace(); + this.shutdown(); + } + } + } + + public void shutdown() { + logger.finer("Shutting down DMAPListener " + this.toString()); + for (DMAPConnection client : clients) { + if (client != null) + client.shutdown(); + } + executorService.shutdown(); + try { + if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) { + executorService.shutdownNow(); + if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) + System.err.println("Pool did not terminate"); + } + } catch (InterruptedException ie) { + executorService.shutdownNow(); + Thread.currentThread().interrupt(); + } + this.interrupt(); + } } diff --git a/src/main/java/dslab/mailbox/MailboxServer.java b/src/main/java/dslab/mailbox/MailboxServer.java index 2b56922..fa0c166 100644 --- a/src/main/java/dslab/mailbox/MailboxServer.java +++ b/src/main/java/dslab/mailbox/MailboxServer.java @@ -75,7 +75,8 @@ public class MailboxServer implements IMailboxServer, Runnable { } this.dmtpListener = new DMTPListener(this.dmtpServerSocket, this.messageStorage); this.dmtpListener.start(); - // TODO spawn listener for user clients (DMAPListener) + this.dmapListener = new DMAPListener(this.dmapServerSocket, this.messageStorage, this.userStorage); + this.dmapListener.start(); this.shell.run(); } @@ -95,6 +96,7 @@ public class MailboxServer implements IMailboxServer, Runnable { try { if (dmapServerSocket != null) dmapServerSocket.close(); + this.dmapListener.interrupt(); } catch (IOException e) { logger.severe("Error closing DMTP serverSocket " + dmapServerSocket.toString()); e.printStackTrace();