Einfacher CLI-E-Mail-Client mit mehreren Accounts


  • Gesperrt

    Hi,

    ich bin mir nicht ganz sicher, ob die Nebenläufigkeit hier korrekt umgesetzt wurde (insbesondere die Funktionsweise des CyclicBarrier - ich wundere mich ehrlich gesagt, weshalb dieser funktioniert).

    Es gibt zwei IMAP-Accounts. Wenn ich "r" eingebe, sollen die Listen der E-Mails erneut geladen werden, wenn ich i eingebe, dann soll der Inhalt der E-Mail mit dem Index i angezeigt werden und wenn ich nichts eingebe, dann soll die Anwendung beendet werden (geordnet).

    Hier ist mein Code:

    import jakarta.mail.*;
    import jakarta.mail.internet.MimeMultipart;
    import java.nio.charset.StandardCharsets;
    import java.util.*;
    import java.util.concurrent.CyclicBarrier;
    import javax.swing.*;
    import org.jsoup.Jsoup;
    import org.jsoup.nodes.Document;
    import org.jsoup.safety.Safelist;
    
    public class Main {
      record Account(
          int index, String host, int port, String user, String password, String inboxName) {}
    
      private static final Map<Account, List<Message>> messages = new LinkedHashMap<>();
      private static int state = 1;
    
      private static final CyclicBarrier barrier =
          new CyclicBarrier(
              2,
              () -> {
                try {
                  while (true) {
                    int i1 = 1;
                    List<Message> tempList = new ArrayList<>();
                    for (Map.Entry<Account, List<Message>> e : messages.entrySet()) {
                      System.out.println(e.getKey().index() + " " + e.getKey().host() + ":");
                      for (Message m : e.getValue()) {
                        System.out.println(i1++ + " " + m.getSentDate() + " " + m.getSubject());
                        tempList.add(m);
                      }
                      System.out.println();
                    }
                    System.out.println("Wich to display? (or r for reload or empty to quit):");
                    String line = new Scanner(System.in, StandardCharsets.UTF_8).nextLine();
                    if (line == null || line.isBlank()) {
                      state = 0;
                      return;
                    }
                    if (line.equals("r")) {
                      state = 1;
                      return;
                    }
                    int i2 = Integer.parseInt(line);
                    display1(tempList.get(i2 - 1));
                  }
                } catch (Exception e) {
                  throw new RuntimeException(e);
                }
              });
    
      public static void main(String[] args) {
        new Thread(
                () -> {
                  try {
                    receive10(
                        new Account(
                            1, "", 993, "", "", "INBOX"));
                  } catch (Exception e) {
                    throw new RuntimeException(e);
                  }
                })
            .start();
        new Thread(
                () -> {
                  try {
                    receive10(
                        new Account(
                            2, "", 993, "", "", "INBOX"));
                  } catch (Exception e) {
                    throw new RuntimeException(e);
                  }
                })
            .start();
      }
    
      private static void receive10(Account ac) throws Exception {
        Store emailStore = null;
        Folder emailFolder = null;
    
        Properties properties = new Properties();
        properties.put("mail.store.protocol", "imap");
        properties.put("mail.imap.ssl.enable", true);
        properties.put("mail.imap.host", ac.host());
        properties.put("mail.imap.port", ac.port());
        Session emailSession = Session.getInstance(properties);
    
        try {
          emailStore = emailSession.getStore();
          emailStore.connect(ac.user(), ac.password());
          System.out.println("Connected: " + ac.host());
    
          emailFolder = emailStore.getFolder(ac.inboxName());
          emailFolder.open(Folder.READ_ONLY);
    
          while (state != 0) {
            List<Message> l = new ArrayList<>();
            int c1 = emailFolder.getMessageCount();
            int c2 = 0;
            for (int i = c1; i > 0 && c2 < 10; i--, c2++) {
              l.add(emailFolder.getMessage(i));
            }
            messages.put(ac, l);
            barrier.await();
          }
        } finally {
          if (emailFolder != null && emailFolder.isOpen()) {
            emailFolder.close(false);
          }
          if (emailStore != null && emailStore.isConnected()) {
            emailStore.close();
          }
          System.out.println("Disconnected: " + ac.host());
        }
      }
    
      private static void display1(Message message) throws Exception {
        StringBuilder sb = new StringBuilder();
        sb.append(message.getSentDate()).append("\n\n");
        for (Address a : message.getFrom()) {
          sb.append(a.toString()).append("\n\n");
        }
        sb.append(message.getSubject()).append("\n\n");
        if (message.isMimeType("text/plain")) {
          sb.append(message.getContent().toString());
        }
        if (message.isMimeType("multipart/*")) {
          MimeMultipart mimeMultipart = (MimeMultipart) message.getContent();
          for (int i = 0; i < mimeMultipart.getCount(); i++) {
            Document document = Jsoup.parse(mimeMultipart.getBodyPart(i).getContent().toString());
            document.outputSettings(new Document.OutputSettings().prettyPrint(false));
            document.select("br").append("\\n");
            document.select("p").prepend("\\n\\n");
            String s = document.html().replaceAll("\\\\n", "\n");
            sb.append(
                    Jsoup.clean(
                        s, "", Safelist.none(), new Document.OutputSettings().prettyPrint(false)))
                .append("\n\n");
          }
        }
    
        JFrame f = new JFrame();
        JTextArea ta = new JTextArea(sb.toString());
        ta.setLineWrap(true);
        f.getContentPane().add(new JScrollPane(ta));
        f.setSize(600, 400);
        f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        f.setVisible(true);
      }
    }
    
    

    Hier sind die Gradle-Dependencies:

    dependencies {
        // https://mvnrepository.com/artifact/com.sun.mail/jakarta.mail
        implementation 'com.sun.mail:jakarta.mail:2.0.1'
        // https://mvnrepository.com/artifact/org.jsoup/jsoup
        implementation 'org.jsoup:jsoup:1.18.1'
    }
    

    Ich wäre froh, wenn da jemand "mal schnell" drüberschauen und Kritik anbringen könnte. Danke.


  • Gesperrt

    Hm, nach ein bisschen Herumspielen, geht es scheinbar doch noch nicht (ganz so einfach).

    Ich möchte die E-Mails der Accounts sowohl parallel als auch sequenziell abrufen können ... (also später soll der Benutzer auswählen können, was er will ...):

    import jakarta.mail.*;
    import jakarta.mail.internet.MimeMultipart;
    import java.nio.charset.StandardCharsets;
    import java.util.*;
    import java.util.concurrent.CyclicBarrier;
    import javax.swing.*;
    import org.jsoup.Jsoup;
    import org.jsoup.nodes.Document;
    import org.jsoup.safety.Safelist;
    
    public class Main {
      record Account(
          int index, String host, int port, String user, String password, String inboxName) {}
    
      private static final List<Account> accounts =
          List.of(
              new Account(1, "", 993, "", "", "INBOX"),
              new Account(2, "", 993, "", "", "INBOX"));
      private static final Map<Account, List<Message>> messages = new LinkedHashMap<>();
    //  private static final Object monitor = new Object();
      private static int state = 1;
    
      private static final CyclicBarrier barrier =
          new CyclicBarrier(
              accounts.size(),
              () -> {
                try {
                  while (true) {
                    int i1 = 1;
                    List<Message> tempList = new ArrayList<>();
                    for (Map.Entry<Account, List<Message>> e : messages.entrySet()) {
                      System.out.println(e.getKey().index() + " " + e.getKey().host() + ":");
                      for (Message m : e.getValue()) {
                        System.out.println(i1++ + " " + m.getSentDate() + " " + m.getSubject());
                        tempList.add(m);
                      }
                      System.out.println();
                    }
                    System.out.println("Wich to display? (or r for reload or empty to quit):");
                    String line = new Scanner(System.in, StandardCharsets.UTF_8).nextLine();
                    if (line == null || line.isBlank()) {
                      state = 0;
                      return;
                    }
                    if (line.equals("r")) {
                      state = 1;
                      return;
                    }
                    int i2 = Integer.parseInt(line);
                    display1(tempList.get(i2 - 1));
                  }
                } catch (Exception e) {
                  throw new RuntimeException(e);
                }
              });
    
      public static void main(String[] args) {
        for (Account ac : accounts) {
          new Thread(
                  () -> {
                    try {
                      receive10(ac);
                    } catch (Exception e) {
                      throw new RuntimeException(e);
                    }
                  })
              .start();
    //      synchronized (monitor) {
    //        try {
    //          monitor.wait();
    //        } catch (InterruptedException e) {
    //          throw new RuntimeException(e);
    //        }
    //      }
        }
      }
    
      private static void receive10(Account ac) throws Exception {
        Store emailStore = null;
        Folder emailFolder = null;
    
        Properties properties = new Properties();
        properties.put("mail.store.protocol", "imap");
        properties.put("mail.imap.ssl.enable", true);
        properties.put("mail.imap.host", ac.host());
        properties.put("mail.imap.port", ac.port());
        Session emailSession = Session.getInstance(properties);
    
        try {
          emailStore = emailSession.getStore();
          emailStore.connect(ac.user(), ac.password());
          System.out.println("Connected: " + ac.host());
    
          emailFolder = emailStore.getFolder(ac.inboxName());
          emailFolder.open(Folder.READ_ONLY);
    
          while (state != 0) {
            System.out.println("Reading: " + ac.host());
            List<Message> l = new ArrayList<>();
            int c1 = emailFolder.getMessageCount();
            int c2 = 0;
            for (int i = c1; i > 0 && c2 < 10; i--, c2++) {
              l.add(emailFolder.getMessage(i));
            }
            messages.put(ac, l);
    //        synchronized (monitor) {
    //          monitor.notifyAll();
    //        }
            barrier.await();
          }
        } finally {
          if (emailFolder != null && emailFolder.isOpen()) {
            emailFolder.close(false);
          }
          if (emailStore != null && emailStore.isConnected()) {
            emailStore.close();
          }
          System.out.println("Disconnected: " + ac.host());
        }
      }
    
      private static void display1(Message message) throws Exception {
        StringBuilder sb = new StringBuilder();
        sb.append(message.getSentDate()).append("\n\n");
        for (Address a : message.getFrom()) {
          sb.append(a.toString()).append("\n\n");
        }
        sb.append(message.getSubject()).append("\n\n");
        if (message.isMimeType("text/plain")) {
          sb.append(message.getContent().toString());
        }
        if (message.isMimeType("multipart/*")) {
          MimeMultipart mimeMultipart = (MimeMultipart) message.getContent();
          for (int i = 0; i < mimeMultipart.getCount(); i++) {
            Document document = Jsoup.parse(mimeMultipart.getBodyPart(i).getContent().toString());
            document.outputSettings(new Document.OutputSettings().prettyPrint(false));
            document.select("br").append("\\n");
            document.select("p").prepend("\\n\\n");
            String s = document.html().replaceAll("\\\\n", "\n");
            sb.append(
                    Jsoup.clean(
                        s, "", Safelist.none(), new Document.OutputSettings().prettyPrint(false)))
                .append("\n\n");
          }
        }
    
        JFrame f = new JFrame();
        JTextArea ta = new JTextArea(sb.toString());
        ta.setLineWrap(true);
        f.getContentPane().add(new JScrollPane(ta));
        f.setSize(600, 400);
        f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        f.setVisible(true);
      }
    }
    
    

    Wenn ich 68-74 und 106-108 EINkommentiere, einmal reload ausführe und danach beende, gibt es einen Deadlock!

    Also, was ist falsch?


  • Gesperrt

    Na ja, man sollte sich schon vorher entscheiden, was man will: async (parallel) oder sync (parallel) ... 😃 Beides zusammen ist nur so semi gut ...

    Außerdem wäre bei wait und notify(All) das A und O, das Conditional-Wait, das heißt, man braucht noch eine zusätzliche Bedingung, auf die man wartet.

    Bei der CyclicBarrier ist dies jedoch nicht so und sie kann auch wiederverwendet werden. 🙂 Thema gelöst.


Anmelden zum Antworten