Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

General: Include more information in course archive #7199

Merged
merged 22 commits into from
Sep 16, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c9382d6
include more information for PE in course archive
tobias-lippert Sep 11, 2023
b4f3793
start implementing export changes
tobias-lippert Sep 12, 2023
cd1b2c6
finish implementation for now
tobias-lippert Sep 12, 2023
ba1c5d9
fix exercise details not included in download
tobias-lippert Sep 14, 2023
34023dc
fix remaining tests, better test archiving of PE
tobias-lippert Sep 14, 2023
6e72f1b
remove unnecessary call to schedule directory deletion
tobias-lippert Sep 14, 2023
9452f1f
Merge branch 'develop' into enhancement/archive-more-information-for-pe
tobias-lippert Sep 14, 2023
5c30d51
address max comments
tobias-lippert Sep 14, 2023
0ab4554
wrap PE export in try catch block
tobias-lippert Sep 14, 2023
9d8e097
Merge branch 'develop' into enhancement/archive-more-information-for-pe
tobias-lippert Sep 15, 2023
c33d041
fixes after merge
tobias-lippert Sep 15, 2023
e0ff895
move all services that have something to do with export to its own pa…
tobias-lippert Sep 15, 2023
d70d9a6
Merge branch 'develop' into enhancement/archive-more-information-for-pe
tobias-lippert Sep 15, 2023
f341d37
add more javadoc and make pe archival export more failsafe
tobias-lippert Sep 15, 2023
eb0ef6d
move missed classes to export package and move data export classes to…
tobias-lippert Sep 15, 2023
454ded1
make constructors public again
tobias-lippert Sep 15, 2023
91a893d
do not pull on get for now and fix directory structure
tobias-lippert Sep 15, 2023
4d754c5
make exercise directory name definitely unique
tobias-lippert Sep 15, 2023
81971b5
fix repo cloning in temp directory not working
tobias-lippert Sep 15, 2023
818f311
fix temporary directory creation
tobias-lippert Sep 15, 2023
18aaada
Merge branch 'develop' into enhancement/archive-more-information-for-pe
Sep 16, 2023
6d93acc
Merge branch 'develop' into enhancement/archive-more-information-for-pe
tobias-lippert Sep 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package de.tum.in.www1.artemis.service;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
Expand All @@ -11,7 +10,6 @@
import java.util.*;
import java.util.stream.Collectors;

import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -43,27 +41,29 @@ public class CourseExamExportService {

private final ZipFileService zipFileService;

private final FileService fileService;
private final TextExerciseWithSubmissionsExportService textExerciseWithSubmissionsExportService;

private final FileUploadSubmissionExportService fileUploadSubmissionExportService;
private final FileUploadExerciseWithSubmissionsExportService fileUploadExerciseWithSubmissionsExportService;

private final TextSubmissionExportService textSubmissionExportService;
private final ModelingExerciseWithSubmissionsExportService modelingExerciseWithSubmissionsExportService;

private final ModelingSubmissionExportService modelingSubmissionExportService;
private final FileService fileService;

private final ExamRepository examRepository;

private final WebsocketMessagingService websocketMessagingService;

public CourseExamExportService(ProgrammingExerciseExportService programmingExerciseExportService, ZipFileService zipFileService, FileService fileService,
FileUploadSubmissionExportService fileUploadSubmissionExportService, TextSubmissionExportService textSubmissionExportService,
ModelingSubmissionExportService modelingSubmissionExportService, WebsocketMessagingService websocketMessagingService, ExamRepository examRepository) {
TextExerciseWithSubmissionsExportService textExerciseWithSubmissionsExportService,
FileUploadExerciseWithSubmissionsExportService fileUploadExerciseWithSubmissionsExportService,
ModelingExerciseWithSubmissionsExportService modelingExerciseWithSubmissionsExportService, WebsocketMessagingService websocketMessagingService,
ExamRepository examRepository) {
this.programmingExerciseExportService = programmingExerciseExportService;
this.zipFileService = zipFileService;
this.fileService = fileService;
this.fileUploadSubmissionExportService = fileUploadSubmissionExportService;
this.textSubmissionExportService = textSubmissionExportService;
this.modelingSubmissionExportService = modelingSubmissionExportService;
this.textExerciseWithSubmissionsExportService = textExerciseWithSubmissionsExportService;
this.fileUploadExerciseWithSubmissionsExportService = fileUploadExerciseWithSubmissionsExportService;
this.modelingExerciseWithSubmissionsExportService = modelingExerciseWithSubmissionsExportService;
this.websocketMessagingService = websocketMessagingService;
this.examRepository = examRepository;
}
Expand All @@ -77,7 +77,7 @@ public CourseExamExportService(ProgrammingExerciseExportService programmingExerc
* @param exportErrors List of failures that occurred during the export
* @return Path to the zip file
*/
public Optional<Path> exportCourse(Course course, String outputDir, List<String> exportErrors) {
public Optional<Path> exportCourse(Course course, Path outputDir, List<String> exportErrors) {
// Used for sending export progress notifications to instructors
var notificationTopic = "/topic/courses/" + course.getId() + "/export-course";
notifyUserAboutExerciseExportState(notificationTopic, CourseExamExportState.RUNNING, List.of("Creating temporary directories..."));
Expand Down Expand Up @@ -119,11 +119,11 @@ public Optional<Path> exportCourse(Course course, String outputDir, List<String>
return exportedCourse;
}

private Optional<Path> zipExportedExercises(String outputDir, List<String> exportErrors, String notificationTopic, Path tmpDir, List<Path> filesToZip) {
private Optional<Path> zipExportedExercises(Path outputDir, List<String> exportErrors, String notificationTopic, Path tmpDir, List<Path> filesToZip) {
// Zip all exported exercises into a single zip file.
notifyUserAboutExerciseExportState(notificationTopic, CourseExamExportState.RUNNING, List.of("Done exporting exercises. Creating course zip..."));
Path courseZip = Path.of(outputDir, tmpDir.getFileName() + ".zip");
var exportedCourse = createCourseZipFile(courseZip, filesToZip, tmpDir, exportErrors);
Path courseZip = outputDir.resolve(tmpDir.getFileName() + ".zip");
var exportedCourse = createCourseZipFile(courseZip, filesToZip, exportErrors);

// Delete temporary directory used for zipping
fileService.scheduleForDirectoryDeletion(tmpDir, 1);
Expand All @@ -142,7 +142,7 @@ private Optional<Path> zipExportedExercises(String outputDir, List<String> expor
* @param exportErrors List of failures that occurred during the export
* @return Path to the zip file
*/
public Optional<Path> exportExam(Exam exam, String outputDir, List<String> exportErrors) {
public Optional<Path> exportExam(Exam exam, Path outputDir, List<String> exportErrors) {
// Used for sending export progress notifications to instructors
var notificationTopic = "/topic/exams/" + exam.getId() + "/export";
notifyUserAboutExerciseExportState(notificationTopic, CourseExamExportState.RUNNING, List.of("Creating temporary directories..."));
Expand Down Expand Up @@ -362,34 +362,37 @@ private List<Path> exportExercises(String notificationTopic, Set<Exercise> exerc
currentProgress++;
notifyUserAboutExerciseExportState(notificationTopic, CourseExamExportState.RUNNING, List.of(currentProgress + "/" + totalExerciseCount + " done"));

// Export programming exercise
if (exercise instanceof ProgrammingExercise programmingExercise) {
// Download the repositories' template, solution, tests and students' repositories
exportedExercises.add(programmingExerciseExportService.exportProgrammingExerciseRepositories(programmingExercise, true, outputDir, exportErrors, reportData));
var exerciseExportDir = outputDir.resolve(exercise.getSanitizedExerciseTitle());
try {
Files.createDirectory(exerciseExportDir);
}
catch (IOException e) {
log.error("Failed to create directory for exercise {} with id {}: {} Going to skip this exercise", exercise.getTitle(), exercise.getId(), e.getMessage());
continue;
}

// Export the other exercises types

// Export options
var submissionsExportOptions = new SubmissionExportOptionsDTO();
submissionsExportOptions.setExportAllParticipants(true);

// The zip file containing student submissions for the other exercise types
Optional<File> exportedSubmissionsFileOrEmpty = Optional.empty();

try {
if (exercise instanceof FileUploadExercise) {
exportedSubmissionsFileOrEmpty = fileUploadSubmissionExportService.exportStudentSubmissions(exercise.getId(), submissionsExportOptions, outputDir, exportErrors,
reportData);
// Export programming exercise
if (exercise instanceof ProgrammingExercise programmingExercise) {
// Download the repositories' template, solution, tests and students' repositories
exportedExercises.add(
programmingExerciseExportService.exportProgrammingExerciseForArchival(programmingExercise, exportErrors, Optional.of(exerciseExportDir), reportData));
}
// Export the other exercises types
else if (exercise instanceof FileUploadExercise) {
exportedExercises.add(fileUploadExerciseWithSubmissionsExportService.exportFileUploadExerciseWithSubmissions(exercise, submissionsExportOptions,
exerciseExportDir, exportErrors, reportData));
}
else if (exercise instanceof TextExercise) {
exportedSubmissionsFileOrEmpty = textSubmissionExportService.exportStudentSubmissions(exercise.getId(), submissionsExportOptions, outputDir, exportErrors,
reportData);
exportedExercises.add(textExerciseWithSubmissionsExportService.exportTextExerciseWithSubmissions(exercise, submissionsExportOptions, exerciseExportDir,
exportErrors, reportData));
}
else if (exercise instanceof ModelingExercise) {
exportedSubmissionsFileOrEmpty = modelingSubmissionExportService.exportStudentSubmissions(exercise.getId(), submissionsExportOptions, outputDir, exportErrors,
reportData);
exportedExercises.add(modelingExerciseWithSubmissionsExportService.exportModelingExerciseWithSubmissions(exercise, submissionsExportOptions, exerciseExportDir,
exportErrors, reportData));
}
else if (exercise instanceof QuizExercise) {
// TODO: Quiz submissions aren't supported yet
Expand All @@ -403,24 +406,6 @@ else if (exercise instanceof QuizExercise) {
catch (Exception e) {
logMessageAndAppendToList("Failed to export exercise '" + exercise.getTitle() + "' (id: " + exercise.getId() + "): " + e.getMessage(), exportErrors, e);
}

// Exported submissions are stored somewhere else, so we move the generated zip file into the
// outputDir (directory where the files needed for the course archive are stored).
if (exportedSubmissionsFileOrEmpty.isPresent()) {
var exportedSubmissionsFile = exportedSubmissionsFileOrEmpty.get();
try {
Path newExportedSubmissionsFilePath = Path.of(outputDir.toString(), exportedSubmissionsFile.getName());
Files.move(exportedSubmissionsFile.toPath(), newExportedSubmissionsFilePath);

exportedExercises.add(newExportedSubmissionsFilePath);

// Delete the directory where the zip was located before it was moved
FileUtils.deleteDirectory(Path.of(exportedSubmissionsFile.getParent()).toFile());
}
catch (IOException e) {
logMessageAndAppendToList("Failed to move file " + exportedSubmissionsFile.toPath() + " to " + outputDir + ".", exportErrors, e);
}
}
}
return exportedExercises.stream().filter(Objects::nonNull).collect(Collectors.toCollection(ArrayList::new));
}
Expand All @@ -429,20 +414,19 @@ else if (exercise instanceof QuizExercise) {
* Creates a zip file out of all the files and directories inside courseDirPath and saves it to
* the directory specified by outputDirPath
*
* @param outputZipFile The path to the zip file that will be created
* @param filesToZip the files to zip together
* @param relativeZipPath the path of the zip files will be relative to this path
* @param outputZipFile The path to the zip file that will be created
* @param filesToZip the files to zip together
* @return The path to the zip file
*/
private Optional<Path> createCourseZipFile(Path outputZipFile, List<Path> filesToZip, Path relativeZipPath, List<String> exportErrors) {
private Optional<Path> createCourseZipFile(Path outputZipFile, List<Path> filesToZip, List<String> exportErrors) {
try {
// Create the parent directories if they don't exist otherwise the zip file cannot be created.
Path parentDir = outputZipFile.getParent();
if (!Files.exists(parentDir)) {
Files.createDirectories(parentDir);
}

zipFileService.createZipFile(outputZipFile, filesToZip, relativeZipPath);
zipFileService.createZipFile(outputZipFile, filesToZip);
log.info("Successfully created zip file: {}", outputZipFile);
return Optional.of(outputZipFile);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
public class CourseService {

@Value("${artemis.course-archives-path}")
private String courseArchivesDirPath;
private Path courseArchivesDirPath;

private final Logger log = LoggerFactory.getLogger(CourseService.class);

Expand Down Expand Up @@ -753,7 +753,7 @@ public void archiveCourse(Course course) {

try {
// Create course archives directory if it doesn't exist
Files.createDirectories(Path.of(courseArchivesDirPath));
Files.createDirectories(courseArchivesDirPath);
log.info("Created the course archives directory at {} because it didn't exist.", courseArchivesDirPath);

// Export the course to the archives' directory.
Expand Down
Loading