001package edu.pdx.cs410J.grader;
002
003import com.google.common.annotations.VisibleForTesting;
004import com.google.common.io.ByteStreams;
005import edu.pdx.cs410J.grader.gradebook.Assignment;
006import edu.pdx.cs410J.grader.gradebook.Grade;
007import edu.pdx.cs410J.grader.gradebook.GradeBook;
008import edu.pdx.cs410J.grader.gradebook.Student;
009import jakarta.mail.Address;
010import jakarta.mail.Message;
011import jakarta.mail.MessagingException;
012import jakarta.mail.internet.InternetAddress;
013
014import java.io.File;
015import java.io.FileOutputStream;
016import java.io.IOException;
017import java.io.InputStream;
018import java.time.LocalDateTime;
019import java.time.ZoneId;
020import java.util.Date;
021import java.util.List;
022import java.util.Optional;
023import java.util.function.Predicate;
024import java.util.stream.Stream;
025
026public abstract class ZipFileSubmissionsProcessor extends StudentEmailAttachmentProcessor {
027  public ZipFileSubmissionsProcessor(File directory, GradeBook gradeBook) {
028    super(directory, gradeBook);
029  }
030
031  @Override
032  public Iterable<? extends String> getSupportedContentTypes() {
033    return List.of("application/zip", "application/x-zip-compressed", "application/x-gzip");
034  }
035
036  @Override
037  public void processAttachment(Message message, String fileName, InputStream inputStream, String contentType) {
038    String studentId;
039    try {
040      studentId = getIdOfStudentInGradeBookWhoSent(message);
041
042    } catch (SubmissionException ex) {
043      logException("While getting student id", ex);
044      return;
045    }
046
047    try {
048      File file = getLocationToWriteFile(fileName, studentId);
049      info("Writing \"" + fileName + "\" to " + file);
050
051      ByteStreams.copy(inputStream, new FileOutputStream(file));
052    } catch (IOException ex) {
053      logException("While writing \"" + fileName + "\" to \"" + directory + "\"", ex);
054    }
055
056    try {
057      noteSubmissionInGradeBook(message);
058
059    } catch (SubmissionException ex) {
060      logException("While noting submission from \"" + fileName + "\"", ex);
061    }
062  }
063
064  private String getIdOfStudentInGradeBookWhoSent(Message message) throws SubmissionException {
065    Student student = getStudentFromGradeBook(message);
066    return student.getId();
067  }
068
069  private void noteSubmissionInGradeBook(Message message) throws SubmissionException {
070    Student student = getStudentFromGradeBook(message);
071    Assignment project = getKoansProjectFromGradeBook();
072    String note = getSubmissionNote(message);
073
074    Grade grade = student.getGrade(project);
075    if (grade == null) {
076      grade = new Grade(project, Grade.NO_GRADE);
077      student.setGrade(project.getName(), grade);
078    }
079    grade.addNote(note);
080    grade.noteSubmission(getSentDate(message));
081  }
082
083  private String getSubmissionNote(Message message) throws SubmissionException {
084    String senderName = getSenderName(message);
085    LocalDateTime sentDate = getSentDate(message);
086
087    return getSubmissionNote(senderName, sentDate);
088  }
089
090  private LocalDateTime getSentDate(Message message) throws SubmissionException {
091    Date sentDate;
092    try {
093      sentDate = message.getSentDate();
094    } catch (MessagingException e) {
095      throw new SubmissionException("While getting the sent date", e);
096    }
097    return LocalDateTime.ofInstant(sentDate.toInstant(), ZoneId.systemDefault());
098  }
099
100  @VisibleForTesting
101  static String getSubmissionNote(String senderName, LocalDateTime submissionTime) {
102    return "Submitted by " + senderName + " on " + Submit.ManifestAttributes.formatSubmissionTime(submissionTime);
103  }
104
105  private Assignment getKoansProjectFromGradeBook() throws SubmissionException {
106    Assignment assignment = gradeBook.getAssignment(getAssignmentName());
107    if (assignment == null) {
108      throw new SubmissionException("Could not find \"koans\" assignment in grade book");
109    }
110    return assignment;
111  }
112
113  protected abstract String getAssignmentName();
114
115  private Student getStudentFromGradeBook(Message message) throws SubmissionException {
116    String senderName = getSenderName(message);
117    Optional<Student> student = getStudentWithName(senderName);
118    if (student.isPresent()) {
119      return student.get();
120    }
121
122    String senderAddress = getSenderEmailAddress(message);
123    student = getStudentWithEmailAddress(senderAddress);
124    return student.orElseThrow(() -> {
125      String m = "Couldn't find student named \"" + senderName + "\" with email address \"" +
126        senderAddress + "\" in gradebook";
127      return new SubmissionException(m);
128    });
129  }
130
131  private Optional<Student> getStudentWithEmailAddress(String senderAddress) {
132    return getStudentsInGradeBook().filter(doesStudentHaveEmailAddress(senderAddress)).findAny();
133  }
134
135  private Predicate<Student> doesStudentHaveEmailAddress(String address) {
136    return student -> student.getEmail().equals(address);
137  }
138
139  private String getSenderEmailAddress(Message message) throws SubmissionException {
140    Address from = getSender(message);
141
142    if (from instanceof InternetAddress) {
143      return getEmailAddress((InternetAddress) from);
144    } else {
145      return from.toString();
146    }
147  }
148
149  private Optional<Student> getStudentWithName(String studentName) {
150    return getStudentsInGradeBook().filter(doesStudentHaveName(studentName)).findAny();
151  }
152
153  private Stream<Student> getStudentsInGradeBook() {
154    return gradeBook.studentsStream();
155  }
156
157  private Predicate<Student> doesStudentHaveName(String studentName) {
158    return student -> getFullName(student).equals(studentName) || getNickName(student).equals(studentName);
159  }
160
161  private String getNickName(Student student) {
162    return student.getNickName() + " " + student.getLastName();
163  }
164
165  private String getFullName(Student student) {
166    return student.getFirstName() + " " + student.getLastName();
167  }
168
169  private File getLocationToWriteFile(String fileName, String studentId) throws IOException {
170    File dir = new File(directory, getAssignmentName());
171    if (!dir.exists() && !dir.mkdirs()) {
172      throw new IOException("Could not create directory \"" + dir + "\"");
173    }
174    return new File(dir, appendFileSuffixToStudentId(fileName, studentId));
175  }
176
177  private String appendFileSuffixToStudentId(String fileName, String studentId) {
178    return studentId + fileName.substring(fileName.lastIndexOf('.'));
179  }
180
181  private String getSenderName(Message message) throws SubmissionException {
182    Address from = getSender(message);
183
184    if (from instanceof InternetAddress) {
185      return getStudentName((InternetAddress) from);
186    } else {
187      return from.toString();
188    }
189  }
190
191  private Address getSender(Message message) throws SubmissionException {
192    Address from;
193    try {
194      from = message.getFrom()[0];
195
196    } catch (MessagingException ex) {
197      throw new SubmissionException("Could not get from address from " + message, ex);
198    }
199    return from;
200  }
201
202  private String getStudentName(InternetAddress from) {
203    if (from.getPersonal() != null) {
204      return from.getPersonal();
205
206    } else {
207      return getEmailAddress(from);
208    }
209  }
210
211  private String getEmailAddress(InternetAddress from) {
212    return from.getAddress();
213  }
214}