Search in sources :

Example 1 with ArchivalReportEntry

use of de.tum.in.www1.artemis.service.archival.ArchivalReportEntry in project ArTEMiS by ls1intum.

the class SubmissionExportService method createZipFileFromParticipations.

/**
 * Creates a zip file from a list of participations for an exercise.
 *
 * The outputDir is used to store the zip file and temporary files used for zipping so make
 * sure to delete it if it's no longer used.
 *
 * @param exercise the exercise in question
 * @param participations a list of participations to include
 * @param enableFilterAfterDueDate true, if all submissions that have been submitted after the due date should not be included in the file
 * @param lateSubmissionFilter an optional date filter for submissions
 * @param outputDir directory to store the temporary files in
 * @param exportErrors a list of errors for submissions that couldn't be exported and are not included in the file
 * @param reportData   a list of all exercises and their statistics
 * @return the zipped file
 * @throws IOException if an error occurred while zipping
 */
private Optional<File> createZipFileFromParticipations(Exercise exercise, List<StudentParticipation> participations, boolean enableFilterAfterDueDate, @Nullable ZonedDateTime lateSubmissionFilter, Path outputDir, List<String> exportErrors, List<ArchivalReportEntry> reportData) throws IOException {
    Course course = exercise.getCourseViaExerciseGroupOrCourseMember();
    // Create unique name for directory
    String zipGroupName = course.getShortName() + "-" + exercise.getTitle() + "-" + exercise.getId();
    String cleanZipGroupName = fileService.removeIllegalCharacters(zipGroupName);
    String zipFileName = cleanZipGroupName + "-" + ZonedDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd-Hmss")) + ".zip";
    // Create directory
    Path submissionsFolderPath = Path.of(outputDir.toString(), "zippedSubmissions", zipGroupName);
    Path zipFilePath = Path.of(outputDir.toString(), "zippedSubmissions", zipFileName);
    File submissionFolder = submissionsFolderPath.toFile();
    if (!submissionFolder.exists() && !submissionFolder.mkdirs()) {
        log.error("Couldn't create dir: {}", submissionFolder);
        exportErrors.add("Cannot create directory: " + submissionFolder.toPath());
        return Optional.empty();
    }
    // Create counter for log entry
    MutableInt skippedEntries = new MutableInt();
    // Save all Submissions
    List<Path> submissionFilePaths = participations.stream().map(participation -> {
        Submission latestSubmission = latestSubmission(participation, enableFilterAfterDueDate, lateSubmissionFilter);
        if (latestSubmission == null) {
            skippedEntries.increment();
            return Optional.<Path>empty();
        }
        // create file path
        String submissionFileName = exercise.getTitle() + "-" + participation.getParticipantIdentifier() + "-" + latestSubmission.getId() + this.getFileEndingForSubmission(latestSubmission);
        Path submissionFilePath = Path.of(submissionsFolderPath.toString(), submissionFileName);
        // store file
        try {
            this.saveSubmissionToFile(exercise, latestSubmission, submissionFilePath.toFile());
            return Optional.of(submissionFilePath);
        } catch (Exception ex) {
            String message = "Could not create file " + submissionFilePath + "  for exporting: " + ex.getMessage();
            log.error(message, ex);
            exportErrors.add(message);
            return Optional.<Path>empty();
        }
    }).flatMap(Optional::stream).collect(Collectors.toList());
    // Add report entry
    reportData.add(new ArchivalReportEntry(exercise, fileService.removeIllegalCharacters(exercise.getTitle()), participations.size(), submissionFilePaths.size(), skippedEntries.intValue()));
    if (submissionFilePaths.isEmpty()) {
        return Optional.empty();
    }
    // zip stores submissions
    try {
        zipFileService.createZipFile(zipFilePath, submissionFilePaths, submissionsFolderPath);
    } finally {
        log.debug("Delete all temporary files");
        fileService.deleteFiles(submissionFilePaths);
    }
    return Optional.of(zipFilePath.toFile());
}
Also used : Path(java.nio.file.Path) ArchivalReportEntry(de.tum.in.www1.artemis.service.archival.ArchivalReportEntry) Submission(de.tum.in.www1.artemis.domain.Submission) MutableInt(org.apache.commons.lang.mutable.MutableInt) Course(de.tum.in.www1.artemis.domain.Course) File(java.io.File) BadRequestAlertException(de.tum.in.www1.artemis.web.rest.errors.BadRequestAlertException) IOException(java.io.IOException)

Example 2 with ArchivalReportEntry

use of de.tum.in.www1.artemis.service.archival.ArchivalReportEntry in project ArTEMiS by ls1intum.

the class SubmissionExportService method exportStudentSubmissions.

/**
 * Exports student submissions to a zip file for an exercise.
 *
 * The outputDir is used to store the zip file and temporary files used for zipping so make
 * sure to delete it if it's no longer used.
 *
 * @param exerciseId the id of the exercise to be exported
 * @param submissionExportOptions the options for the export
 * @param outputDir directory to store the temporary files in
 * @param exportErrors a list of errors for submissions that couldn't be exported and are not included in the file
 * @param reportData   a list of all exercises and their statistics
 * @return a reference to the zipped file
 * @throws IOException if an error occurred while zipping
 */
public Optional<File> exportStudentSubmissions(Long exerciseId, SubmissionExportOptionsDTO submissionExportOptions, Path outputDir, List<String> exportErrors, List<ArchivalReportEntry> reportData) throws IOException {
    Optional<Exercise> exerciseOpt = exerciseRepository.findWithEagerStudentParticipationsStudentAndSubmissionsById(exerciseId);
    if (exerciseOpt.isEmpty()) {
        return Optional.empty();
    }
    Exercise exercise = exerciseOpt.get();
    // Select the participations that should be exported
    List<StudentParticipation> exportedStudentParticipations;
    if (submissionExportOptions.isExportAllParticipants()) {
        exportedStudentParticipations = new ArrayList<>(exercise.getStudentParticipations());
    } else {
        List<String> participantIds = Arrays.stream(submissionExportOptions.getParticipantIdentifierList().split(",")).map(String::trim).toList();
        exportedStudentParticipations = exercise.getStudentParticipations().stream().filter(participation -> participantIds.contains(participation.getParticipantIdentifier())).collect(Collectors.toList());
    }
    boolean enableFilterAfterDueDate = false;
    ZonedDateTime filterLateSubmissionsDate = null;
    if (submissionExportOptions.isFilterLateSubmissions()) {
        if (submissionExportOptions.getFilterLateSubmissionsDate() == null) {
            enableFilterAfterDueDate = true;
        } else {
            filterLateSubmissionsDate = submissionExportOptions.getFilterLateSubmissionsDate();
        }
    }
    // Sort the student participations by id
    exportedStudentParticipations.sort(Comparator.comparing(DomainObject::getId));
    return this.createZipFileFromParticipations(exercise, exportedStudentParticipations, enableFilterAfterDueDate, filterLateSubmissionsDate, outputDir, exportErrors, reportData);
}
Also used : Exercise(de.tum.in.www1.artemis.domain.Exercise) ZonedDateTime(java.time.ZonedDateTime) StudentParticipation(de.tum.in.www1.artemis.domain.participation.StudentParticipation)

Example 3 with ArchivalReportEntry

use of de.tum.in.www1.artemis.service.archival.ArchivalReportEntry in project Artemis by ls1intum.

the class SubmissionExportService method exportStudentSubmissions.

/**
 * Exports student submissions to a zip file for an exercise.
 *
 * The outputDir is used to store the zip file and temporary files used for zipping so make
 * sure to delete it if it's no longer used.
 *
 * @param exerciseId the id of the exercise to be exported
 * @param submissionExportOptions the options for the export
 * @param outputDir directory to store the temporary files in
 * @param exportErrors a list of errors for submissions that couldn't be exported and are not included in the file
 * @param reportData   a list of all exercises and their statistics
 * @return a reference to the zipped file
 * @throws IOException if an error occurred while zipping
 */
public Optional<File> exportStudentSubmissions(Long exerciseId, SubmissionExportOptionsDTO submissionExportOptions, Path outputDir, List<String> exportErrors, List<ArchivalReportEntry> reportData) throws IOException {
    Optional<Exercise> exerciseOpt = exerciseRepository.findWithEagerStudentParticipationsStudentAndSubmissionsById(exerciseId);
    if (exerciseOpt.isEmpty()) {
        return Optional.empty();
    }
    Exercise exercise = exerciseOpt.get();
    // Select the participations that should be exported
    List<StudentParticipation> exportedStudentParticipations;
    if (submissionExportOptions.isExportAllParticipants()) {
        exportedStudentParticipations = new ArrayList<>(exercise.getStudentParticipations());
    } else {
        List<String> participantIds = Arrays.stream(submissionExportOptions.getParticipantIdentifierList().split(",")).map(String::trim).toList();
        exportedStudentParticipations = exercise.getStudentParticipations().stream().filter(participation -> participantIds.contains(participation.getParticipantIdentifier())).collect(Collectors.toList());
    }
    boolean enableFilterAfterDueDate = false;
    ZonedDateTime filterLateSubmissionsDate = null;
    if (submissionExportOptions.isFilterLateSubmissions()) {
        if (submissionExportOptions.getFilterLateSubmissionsDate() == null) {
            enableFilterAfterDueDate = true;
        } else {
            filterLateSubmissionsDate = submissionExportOptions.getFilterLateSubmissionsDate();
        }
    }
    // Sort the student participations by id
    exportedStudentParticipations.sort(Comparator.comparing(DomainObject::getId));
    return this.createZipFileFromParticipations(exercise, exportedStudentParticipations, enableFilterAfterDueDate, filterLateSubmissionsDate, outputDir, exportErrors, reportData);
}
Also used : Exercise(de.tum.in.www1.artemis.domain.Exercise) ZonedDateTime(java.time.ZonedDateTime) StudentParticipation(de.tum.in.www1.artemis.domain.participation.StudentParticipation)

Example 4 with ArchivalReportEntry

use of de.tum.in.www1.artemis.service.archival.ArchivalReportEntry in project Artemis by ls1intum.

the class ProgrammingExerciseExportService method exportProgrammingExerciseRepositories.

/**
 * Export instructor repositories and optionally students' repositories in a zip file.
 *
 * The outputDir is used to store the zip file and temporary files used for zipping so make
 * sure to delete it if it's no longer used.
 *
 * @param exercise              the programming exercise
 * @param includingStudentRepos flag for including the students repos as well
 * @param outputDir             the path to a directory that will be used to store the zipped programming exercise.
 * @param exportErrors          List of failures that occurred during the export
 * @param reportData            List of all exercises and their statistics
 * @return the path to the zip file
 */
public Path exportProgrammingExerciseRepositories(ProgrammingExercise exercise, Boolean includingStudentRepos, Path outputDir, List<String> exportErrors, List<ArchivalReportEntry> reportData) {
    log.info("Exporting programming exercise {} with title {}", exercise.getId(), exercise.getTitle());
    // List to add paths of files that should be contained in the zip folder of exported programming exercise repositories:
    // i.e., student repositories (if `includingStudentRepos` is true), instructor repositories template, solution and tests
    var pathsToBeZipped = new ArrayList<Path>();
    if (includingStudentRepos) {
        // Lazy load student participation, sort by id, and set the export options
        var studentParticipations = studentParticipationRepository.findByExerciseId(exercise.getId()).stream().map(studentParticipation -> (ProgrammingExerciseStudentParticipation) studentParticipation).sorted(Comparator.comparing(DomainObject::getId)).collect(Collectors.toList());
        var exportOptions = new RepositoryExportOptionsDTO();
        exportOptions.setHideStudentNameInZippedFolder(false);
        // Export student repositories and add them to list
        var exportedStudentRepositoryFiles = exportStudentRepositories(exercise, studentParticipations, exportOptions, outputDir, exportErrors).stream().filter(Objects::nonNull).toList();
        pathsToBeZipped.addAll(exportedStudentRepositoryFiles);
    }
    // Export the template, solution, and tests repositories and add them to list
    pathsToBeZipped.add(exportInstructorRepositoryForExercise(exercise.getId(), RepositoryType.TEMPLATE, outputDir, exportErrors).map(File::toPath).orElse(null));
    pathsToBeZipped.add(exportInstructorRepositoryForExercise(exercise.getId(), RepositoryType.SOLUTION, outputDir, exportErrors).map(File::toPath).orElse(null));
    pathsToBeZipped.add(exportInstructorRepositoryForExercise(exercise.getId(), RepositoryType.TESTS, outputDir, exportErrors).map(File::toPath).orElse(null));
    List<AuxiliaryRepository> auxiliaryRepositories = auxiliaryRepositoryRepository.findByExerciseId(exercise.getId());
    // Export the auxiliary repositories and add them to list
    auxiliaryRepositories.forEach(auxiliaryRepository -> {
        pathsToBeZipped.add(exportInstructorAuxiliaryRepositoryForExercise(exercise.getId(), auxiliaryRepository, outputDir, exportErrors).map(File::toPath).orElse(null));
    });
    // Setup path to store the zip file for the exported repositories
    var timestamp = ZonedDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd-Hmss"));
    String filename = exercise.getCourseViaExerciseGroupOrCourseMember().getShortName() + "-" + exercise.getTitle() + "-" + exercise.getId() + "-" + timestamp + ".zip";
    String cleanFilename = fileService.removeIllegalCharacters(filename);
    Path pathToZippedExercise = Path.of(outputDir.toString(), cleanFilename);
    // Remove null elements and get the file path of each file to be included, i.e. each entry in the pathsToBeZipped list
    List<Path> includedFilePathsNotNull = pathsToBeZipped.stream().filter(Objects::nonNull).collect(Collectors.toList());
    String cleanProjectName = fileService.removeIllegalCharacters(exercise.getProjectName());
    // Add report entry, programming repositories cannot be skipped
    reportData.add(new ArchivalReportEntry(exercise, cleanProjectName, pathsToBeZipped.size(), includedFilePathsNotNull.size(), 0));
    try {
        // Only create zip file if there's files to zip
        if (includedFilePathsNotNull.isEmpty()) {
            String info = "Will not export programming exercise " + exercise.getId() + " with title " + exercise.getTitle() + " because it's empty";
            log.info(info);
            exportErrors.add(info);
            return null;
        }
        // Create the zip folder of the exported programming exercise and return the path to the created folder
        zipFileService.createZipFile(pathToZippedExercise, includedFilePathsNotNull, false);
        return pathToZippedExercise;
    } catch (Exception e) {
        var error = "Failed to export programming exercise because the zip file " + pathToZippedExercise + " could not be created: " + e.getMessage();
        log.info(error);
        exportErrors.add(error);
        return null;
    }
}
Also used : Path(java.nio.file.Path) XPath(javax.xml.xpath.XPath) RepositoryExportOptionsDTO(de.tum.in.www1.artemis.web.rest.dto.RepositoryExportOptionsDTO) ArchivalReportEntry(de.tum.in.www1.artemis.service.archival.ArchivalReportEntry) File(java.io.File) GitException(de.tum.in.www1.artemis.exception.GitException) GitAPIException(org.eclipse.jgit.api.errors.GitAPIException) UncheckedIOException(java.io.UncheckedIOException) SAXException(org.xml.sax.SAXException) TransformerException(javax.xml.transform.TransformerException) XPathException(javax.xml.xpath.XPathException) IOException(java.io.IOException) ParserConfigurationException(javax.xml.parsers.ParserConfigurationException)

Example 5 with ArchivalReportEntry

use of de.tum.in.www1.artemis.service.archival.ArchivalReportEntry in project ArTEMiS by ls1intum.

the class ProgrammingExerciseExportService method exportProgrammingExerciseRepositories.

/**
 * Export instructor repositories and optionally students' repositories in a zip file.
 *
 * The outputDir is used to store the zip file and temporary files used for zipping so make
 * sure to delete it if it's no longer used.
 *
 * @param exercise              the programming exercise
 * @param includingStudentRepos flag for including the students repos as well
 * @param outputDir             the path to a directory that will be used to store the zipped programming exercise.
 * @param exportErrors          List of failures that occurred during the export
 * @param reportData            List of all exercises and their statistics
 * @return the path to the zip file
 */
public Path exportProgrammingExerciseRepositories(ProgrammingExercise exercise, Boolean includingStudentRepos, Path outputDir, List<String> exportErrors, List<ArchivalReportEntry> reportData) {
    log.info("Exporting programming exercise {} with title {}", exercise.getId(), exercise.getTitle());
    // List to add paths of files that should be contained in the zip folder of exported programming exercise repositories:
    // i.e., student repositories (if `includingStudentRepos` is true), instructor repositories template, solution and tests
    var pathsToBeZipped = new ArrayList<Path>();
    if (includingStudentRepos) {
        // Lazy load student participation, sort by id, and set the export options
        var studentParticipations = studentParticipationRepository.findByExerciseId(exercise.getId()).stream().map(studentParticipation -> (ProgrammingExerciseStudentParticipation) studentParticipation).sorted(Comparator.comparing(DomainObject::getId)).collect(Collectors.toList());
        var exportOptions = new RepositoryExportOptionsDTO();
        exportOptions.setHideStudentNameInZippedFolder(false);
        // Export student repositories and add them to list
        var exportedStudentRepositoryFiles = exportStudentRepositories(exercise, studentParticipations, exportOptions, outputDir, exportErrors).stream().filter(Objects::nonNull).toList();
        pathsToBeZipped.addAll(exportedStudentRepositoryFiles);
    }
    // Export the template, solution, and tests repositories and add them to list
    pathsToBeZipped.add(exportInstructorRepositoryForExercise(exercise.getId(), RepositoryType.TEMPLATE, outputDir, exportErrors).map(File::toPath).orElse(null));
    pathsToBeZipped.add(exportInstructorRepositoryForExercise(exercise.getId(), RepositoryType.SOLUTION, outputDir, exportErrors).map(File::toPath).orElse(null));
    pathsToBeZipped.add(exportInstructorRepositoryForExercise(exercise.getId(), RepositoryType.TESTS, outputDir, exportErrors).map(File::toPath).orElse(null));
    List<AuxiliaryRepository> auxiliaryRepositories = auxiliaryRepositoryRepository.findByExerciseId(exercise.getId());
    // Export the auxiliary repositories and add them to list
    auxiliaryRepositories.forEach(auxiliaryRepository -> {
        pathsToBeZipped.add(exportInstructorAuxiliaryRepositoryForExercise(exercise.getId(), auxiliaryRepository, outputDir, exportErrors).map(File::toPath).orElse(null));
    });
    // Setup path to store the zip file for the exported repositories
    var timestamp = ZonedDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd-Hmss"));
    String filename = exercise.getCourseViaExerciseGroupOrCourseMember().getShortName() + "-" + exercise.getTitle() + "-" + exercise.getId() + "-" + timestamp + ".zip";
    String cleanFilename = fileService.removeIllegalCharacters(filename);
    Path pathToZippedExercise = Path.of(outputDir.toString(), cleanFilename);
    // Remove null elements and get the file path of each file to be included, i.e. each entry in the pathsToBeZipped list
    List<Path> includedFilePathsNotNull = pathsToBeZipped.stream().filter(Objects::nonNull).collect(Collectors.toList());
    String cleanProjectName = fileService.removeIllegalCharacters(exercise.getProjectName());
    // Add report entry, programming repositories cannot be skipped
    reportData.add(new ArchivalReportEntry(exercise, cleanProjectName, pathsToBeZipped.size(), includedFilePathsNotNull.size(), 0));
    try {
        // Only create zip file if there's files to zip
        if (includedFilePathsNotNull.isEmpty()) {
            String info = "Will not export programming exercise " + exercise.getId() + " with title " + exercise.getTitle() + " because it's empty";
            log.info(info);
            exportErrors.add(info);
            return null;
        }
        // Create the zip folder of the exported programming exercise and return the path to the created folder
        zipFileService.createZipFile(pathToZippedExercise, includedFilePathsNotNull, false);
        return pathToZippedExercise;
    } catch (Exception e) {
        var error = "Failed to export programming exercise because the zip file " + pathToZippedExercise + " could not be created: " + e.getMessage();
        log.info(error);
        exportErrors.add(error);
        return null;
    }
}
Also used : Path(java.nio.file.Path) XPath(javax.xml.xpath.XPath) RepositoryExportOptionsDTO(de.tum.in.www1.artemis.web.rest.dto.RepositoryExportOptionsDTO) ArchivalReportEntry(de.tum.in.www1.artemis.service.archival.ArchivalReportEntry) File(java.io.File) GitException(de.tum.in.www1.artemis.exception.GitException) GitAPIException(org.eclipse.jgit.api.errors.GitAPIException) UncheckedIOException(java.io.UncheckedIOException) SAXException(org.xml.sax.SAXException) TransformerException(javax.xml.transform.TransformerException) XPathException(javax.xml.xpath.XPathException) IOException(java.io.IOException) ParserConfigurationException(javax.xml.parsers.ParserConfigurationException)

Aggregations

ArchivalReportEntry (de.tum.in.www1.artemis.service.archival.ArchivalReportEntry)4 File (java.io.File)4 IOException (java.io.IOException)4 Path (java.nio.file.Path)4 Course (de.tum.in.www1.artemis.domain.Course)2 Exercise (de.tum.in.www1.artemis.domain.Exercise)2 Submission (de.tum.in.www1.artemis.domain.Submission)2 StudentParticipation (de.tum.in.www1.artemis.domain.participation.StudentParticipation)2 GitException (de.tum.in.www1.artemis.exception.GitException)2 RepositoryExportOptionsDTO (de.tum.in.www1.artemis.web.rest.dto.RepositoryExportOptionsDTO)2 BadRequestAlertException (de.tum.in.www1.artemis.web.rest.errors.BadRequestAlertException)2 UncheckedIOException (java.io.UncheckedIOException)2 ZonedDateTime (java.time.ZonedDateTime)2 ParserConfigurationException (javax.xml.parsers.ParserConfigurationException)2 TransformerException (javax.xml.transform.TransformerException)2 XPath (javax.xml.xpath.XPath)2 XPathException (javax.xml.xpath.XPathException)2 MutableInt (org.apache.commons.lang.mutable.MutableInt)2 GitAPIException (org.eclipse.jgit.api.errors.GitAPIException)2 SAXException (org.xml.sax.SAXException)2