LionService.java

/*
 * Copyright © 2024-2025 The CTAN Team and individual authors
 *
 * This file is distributed under the 3-clause BSD license.
 * See file LICENSE for details.
 */
package org.ctan.site.services.content;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import org.ctan.site.CtanConfiguration.ContentConfig;
import org.ctan.site.services.DateUtils;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NonNull;

/**
 * The class <code>LionService</code> contains the service to access the lion
 * directory.
 *
 * @author <a href="mailto:gene@ctan.org">Gerd Neugebauer</a>
 */
public class LionService {

    /**
     * The class <code>FileListTo</code> contains a transport object for the
     * list resource.
     */
    @Getter
    @Builder
    @AllArgsConstructor
    @SuppressFBWarnings(value = "EI_EXPOSE_REP")
    public static class FileListTo {

        /**
         * The field <code>readme</code> contains the optional name of a README
         * file.
         */
        private String readme;

        /**
         * The field <code>files</code> contains the files contained.
         */
        private List<FileTo> files;
    }

    /**
     * The class <code>FileTo</code> contains the transport object for the list
     * resource.
     */
    @Getter
    @Builder
    @AllArgsConstructor
    public static class FileTo {

        /**
         * The field <code>name</code> contains the file name.
         */
        private String name;

        /**
         * The field <code>size</code> contains the file size.
         */
        private long size;

        /**
         * The field <code>mtime</code> contains the modification time.
         */
        private String mtime;

        /**
         * The field <code>type</code> contains the type.
         */
        private char type;
    }

    /**
     * The field <code>CTAN_LION</code> contains the name of the directory.
     */
    private static final String CTAN_LION = "ctan-lion";

    /**
     * The field <code>dir</code> contains the directory of the lion scans.
     */
    private File dir;

    /**
     * This is the constructor for the class <code>LionService</code>.
     *
     * @param config the content configuration
     */
    @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW")
    public LionService(@NonNull ContentConfig config) {

        var base = config.getDirectory();
        if (base == null || "".equals(base) || !new File(base).isDirectory()) {
            throw new IllegalArgumentException(
                "ctan.content.directory is not a directory " + base);
        }
        this.dir = new File(base, CTAN_LION);
        if (!this.dir.isDirectory()) {
            throw new IllegalArgumentException(
                "ctan.content has no directory '" + CTAN_LION + "'");
        }
    }

    /**
     * The method <code>content</code> provides means to read the contents of a
     * file.
     *
     * @param name the name of the file
     * @return the contents or {@code null}
     * @throws IOException in case of an I/O error
     * @throws FileNotFoundException in case the file does not exist
     */
    public byte[] content(@NonNull String name)
        throws FileNotFoundException,
            IOException {

        if (name.contains("/")) {
            return null;
        }
        var f = new File(dir, name);
        try (var in = new FileInputStream(f)) {
            return in.readAllBytes();
        }
    }

    /**
     * The method <code>list</code> provides means to retrieve the directory
     * listing for the lion files.
     *
     * @return a transport object
     */
    public FileListTo list() {

        String readme;
        try {
            readme = Files.readString(new File(dir, "README").toPath());
        } catch (IOException e) {
            readme = "";
        }
        List<FileTo> files =
            Arrays.stream(dir.listFiles())
                .sorted((a, b) -> a.compareTo(b))
                .map(f -> FileTo.builder()
                    .name(f.getName())
                    .mtime(
                        DateUtils.formatDateTime(f.lastModified()))
                    .size(f.length())
                    .type('f')
                    .build())
                .collect(Collectors.toList());
        return FileListTo.builder()
            .readme(readme)
            .files(files)
            .build();
    }
}