Einfacher CLI-E-Mail-Client mit mehreren Accounts
-
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.
-
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?
-
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.