001package edu.pdx.cs410J.grader.poa;
002
003import com.google.common.annotations.VisibleForTesting;
004import com.google.common.eventbus.EventBus;
005import com.google.common.eventbus.Subscribe;
006import com.google.common.io.CharStreams;
007import com.google.inject.Inject;
008import com.google.inject.Singleton;
009import com.google.inject.name.Named;
010import edu.pdx.cs410J.grader.EmailAttachmentProcessor;
011import edu.pdx.cs410J.grader.GraderEmailAccount;
012import jakarta.mail.Message;
013import jakarta.mail.MessagingException;
014
015import java.io.IOException;
016import java.io.InputStream;
017import java.io.InputStreamReader;
018import java.io.StringWriter;
019import java.time.LocalDateTime;
020import java.time.ZoneId;
021import java.util.Arrays;
022import java.util.Date;
023import java.util.concurrent.Executor;
024import java.util.concurrent.Future;
025import java.util.concurrent.FutureTask;
026
027@Singleton
028public class POASubmissionsDownloader {
029
030  static final String POA_FOLDER_NAME = "poa";
031  private final EventBus bus;
032  private final Executor executor;
033
034  @Inject
035  public POASubmissionsDownloader(EventBus bus, @Named("POADownloaderExecutor") Executor executor) {
036    this.bus = bus;
037    this.executor = executor;
038    this.bus.register(this);
039  }
040
041  @Subscribe
042  public void downloadSubmissions(EmailCredentials credentials) throws MessagingException {
043    GraderEmailAccount account = new GraderEmailAccount(credentials.getEmailAddress(), credentials.getPassword(), this::fireStatusMessage);
044    downloadSubmissions(account);
045  }
046
047  @VisibleForTesting
048  Future<?> downloadSubmissions(String emailServerHostName, int emailServerHostPort, EmailCredentials credentials) {
049      GraderEmailAccount account = new GraderEmailAccount(emailServerHostName, emailServerHostPort, credentials.getEmailAddress(), credentials.getPassword(), true, this::fireStatusMessage);
050      return downloadSubmissions(account);
051    }
052
053  private Future<?> downloadSubmissions(GraderEmailAccount account) {
054    FutureTask<Void> future = new FutureTask<>(() -> account.fetchAttachmentsFromUnreadMessagesInFolder(POA_FOLDER_NAME, new POAAttachmentProcessor()), null);
055    this.executor.execute(future);
056    return future;
057  }
058
059  private void fireStatusMessage(String statusMessage) {
060    this.bus.post(new StatusMessage(statusMessage));
061  }
062
063  private void extractPOASubmissionFromAttachment(Message message, InputStream inputStream, String contentType) {
064    try {
065      String submitter = getSender(message);
066      LocalDateTime submitTime = getTimeMessageWasSent(message);
067      String content = extractPOAContentFrom(inputStream);
068      POASubmission submission = POASubmission.builder()
069        .setSubject(message.getSubject())
070        .setSubmitter(submitter)
071        .setSubmitTime(submitTime)
072        .setContent(content)
073        .setContentType(contentType)
074        .create();
075
076      this.bus.post(submission);
077
078    } catch (IOException | MessagingException ex) {
079      throw new IllegalStateException("While working with message", ex);
080    }
081
082  }
083
084  private String extractPOAContentFrom(InputStream inputStream) throws IOException {
085    StringWriter sw = new StringWriter();
086    CharStreams.copy(new InputStreamReader(inputStream), sw);
087    return sw.toString();
088  }
089
090  private LocalDateTime getTimeMessageWasSent(Message message) throws MessagingException {
091    Date date = message.getSentDate();
092    return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
093  }
094
095  private String getSender(Message message) throws MessagingException {
096    return message.getFrom()[0].toString();
097  }
098
099  private class POAAttachmentProcessor implements EmailAttachmentProcessor {
100    @Override
101    public void processAttachment(Message message, String fileName, InputStream inputStream, String contentType) {
102      extractPOASubmissionFromAttachment(message, inputStream, contentType);
103    }
104
105    @Override
106    public Iterable<? extends String> getSupportedContentTypes() {
107      return Arrays.asList("text/plain", "text/html");
108    }
109  }
110}