TemporaryAssetFolder.java

/*
 * Copyright (c) 2017, Stein Eldar Johnsen
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package net.morimekta.file;

import java.io.BufferedInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.Files.newOutputStream;
import static java.nio.file.StandardOpenOption.APPEND;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;

/**
 * A temporary asset folder where an arbitrary number of files can be added
 * to and should guarantee complete cleanup on close. Example usage:
 *
 * <pre>{@code
 * try (TemporaryAssetFolder tmp = new TemporaryAssetFolder(base)) {
 *     // do stuff
 * }
 * // is all deleted.
 * }</pre>
 * <p>
 * This will create a temp folder into the base directory for each run, where
 * any number of files or sub-folders can be added. All of it will be removed
 * immediately on close.
 */
public class TemporaryAssetFolder implements Closeable {
    private static final String PARENT_DIR = "..";

    private final Path path;

    public TemporaryAssetFolder() throws IOException {
        this(Paths.get("/tmp/"));
    }

    public TemporaryAssetFolder(Path baseTempDirectory) throws IOException {
        this(baseTempDirectory, "tmp");
    }

    public TemporaryAssetFolder(Path baseTempDirectory, String prefix) throws IOException {
        path = Files.createTempDirectory(baseTempDirectory, prefix);
    }

    // --- list files ---

    public Collection<Path> list() throws IOException {
        return FileUtil.list(path);
    }

    public Collection<Path> list(boolean recursive) throws IOException {
        return FileUtil.list(path, recursive);
    }

    // ---

    public Path getPath() {
        return path;
    }

    public File getFile() {
        return getPath().toFile();
    }

    public Path resolvePath(String name) {
        if (name.startsWith(File.separator)) {
            throw new IllegalArgumentException("Absolute path not allowed in file name");
        } else if (name.contains(File.separator + PARENT_DIR + File.separator) ||
                   name.startsWith(PARENT_DIR + File.separator)) {
            throw new IllegalArgumentException("Up path not allowed in file name");
        }
        return getPath().resolve(name);
    }

    public File resolveFile(String name) {
        return resolvePath(name).toFile();
    }

    // --- Add content

    public Path put(String name, byte[] content) throws IOException {
        Path file = resolvePath(name);
        Path parent = file.getParent();
        if (parent != null) {
            Files.createDirectories(parent);
        }
        return Files.write(file, content, TRUNCATE_EXISTING, CREATE);
    }

    public Path put(String name, CharSequence content) throws IOException {
        Path file = resolvePath(name);
        Path parent = file.getParent();
        if (parent != null) {
            Files.createDirectories(parent);
        }
        return Files.writeString(file, content, TRUNCATE_EXISTING, CREATE);
    }

    public OutputStream getOutputStream(String name) throws IOException {
        return getOutputStream(name, false);
    }

    public OutputStream getOutputStream(String name, boolean append) throws IOException {
        Path file = resolvePath(name);
        Path parent = file.getParent();
        if (parent != null) {
            Files.createDirectories(parent);
        }
        return newOutputStream(file, CREATE, append ? APPEND : TRUNCATE_EXISTING);
    }

    public Writer getWriter(String name) throws IOException {
        return getWriter(name, false);
    }

    public Writer getWriter(String name, boolean append) throws IOException {
        return new OutputStreamWriter(getOutputStream(name, append), UTF_8);
    }

    // --- Get Content ---

    public String getString(String name) throws IOException {
        return new String(getBytes(name), UTF_8);
    }

    public byte[] getBytes(String name) throws IOException {
        try (InputStream in = getInputStream(name);
             BufferedInputStream bis = new BufferedInputStream(in)) {
            return bis.readAllBytes();
        }
    }

    public Reader getReader(String name) throws IOException {
        return new InputStreamReader(getInputStream(name), UTF_8);
    }

    public InputStream getInputStream(String name) throws IOException {
        return Files.newInputStream(resolvePath(name));
    }

    @Override
    public void close() throws IOException {
        FileUtil.deleteRecursively(path);
    }
}