Retention.java

package io.github.giulong.spectrum.utils;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import io.github.giulong.spectrum.interfaces.reports.CanProduceMetadata;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.nio.file.attribute.FileTime;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;

import static java.lang.Integer.MAX_VALUE;
import static java.lang.Math.clamp;
import static java.lang.Math.max;
import static java.time.ZoneId.systemDefault;
import static java.time.temporal.ChronoUnit.DAYS;
import static java.util.Comparator.comparing;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.partitioningBy;

@Slf4j
@Getter
public class Retention {

    @JsonIgnore
    private final MetadataManager metadataManager = MetadataManager.getInstance();

    @JsonIgnore
    private final FileUtils fileUtils = FileUtils.getInstance();

    @SuppressWarnings("FieldMayBeFinal")
    @JsonPropertyDescription("Number of reports to retain. Older ones will be deleted")
    private int total = MAX_VALUE;

    @SuppressWarnings("unused")
    @JsonPropertyDescription("Number of successful reports to retain. Older ones will be deleted")
    private int successful;

    @SuppressWarnings("FieldMayBeFinal")
    @JsonPropertyDescription("Number of days after which reports will be deleted")
    private int days = MAX_VALUE;

    public void deleteArtifactsFrom(final List<File> files, final CanProduceMetadata metadataProducer) {
        final FixedSizeQueue<File> successfulQueue = metadataManager.getSuccessfulQueueOf(metadataProducer);
        final int successfulLimit = max(0, successful);

        log.debug("Finding at most {} successful files among {}", successfulLimit, files.size());
        final List<File> successfulFilesToKeep = files
                .stream()
                .filter(file -> successfulQueue.contains(file.getAbsoluteFile()))
                .limit(successfulLimit)
                .toList();

        final int successfulToKeep = successfulFilesToKeep.size();
        final int remainingTotalToKeep = max(0, total - successfulToKeep);
        final Map<Boolean, List<File>> partitionedFiles = files
                .stream()
                .filter(not(successfulFilesToKeep::contains))
                .sorted(comparing(fileUtils::getCreationTimeOf))
                .collect(partitioningBy(this::isOld));

        final List<File> youngFiles = partitionedFiles.get(false);

        log.debug("Deleting young artifacts. Successful to keep: {}, Among {}, at most {} will be kept", successfulToKeep, youngFiles.size(), remainingTotalToKeep);
        deleteFrom(youngFiles, remainingTotalToKeep);

        log.debug("Deleting all remaining old artifacts");
        deleteFrom(partitionedFiles.get(true), 0);
    }

    void deleteFrom(final List<File> files, final int maxToKeep) {
        final int size = files.size();
        final int toKeep = clamp(maxToKeep, 0, size);
        final int toDelete = size - toKeep;

        log.debug("Files are {}. {} will be kept, {} will be deleted", size, toKeep, toDelete);
        files.stream().limit(toDelete).forEach(fileUtils::delete);
    }

    boolean isOld(final File file) {
        final FileTime creationTime = fileUtils.getCreationTimeOf(file);
        final LocalDateTime dateTime = LocalDateTime.ofInstant(creationTime.toInstant(), systemDefault());
        final LocalDateTime today = LocalDateTime.now();
        final long difference = DAYS.between(dateTime, today);
        final boolean old = difference >= days;

        log.debug("Report '{}' with date {} was generated {} days ago. Retention days are {}. Is it old? {}", file, dateTime, difference, days, old);
        return old;
    }
}