All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ninja.leaping.configurate.loader.AtomicFileOutputStream Maven / Gradle / Ivy

/**
 * Configurate
 * Copyright (C) zml and Configurate contributors
 *
 * Licensed 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 ninja.leaping.configurate.loader;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;

/**
 * This class implements a wrapper around file output streams that allows for an atomic write on platforms that support atomic file moves.
 * This means that programs that crash mid-write will not leave a partially-written file overwriting the actual permissions file
 */
public class AtomicFileOutputStream extends FilterOutputStream {
    private static final boolean NIO_PATH_SUPPORTED;
    static {
         boolean pathSupported;
        try {
            Class.forName("java.nio.file.Path");
            pathSupported = true;
        } catch (ClassNotFoundException e) {
            pathSupported = false;
        }
        NIO_PATH_SUPPORTED = pathSupported;
    }
    private final File targetFile, writeFile, oldFile;
    public AtomicFileOutputStream(File file) throws IOException {
        super(null);
        writeFile = File.createTempFile(file.getPath().replaceAll("\\\\|/|:", "-"), null, file.getParentFile());
        targetFile = file;
        //writeFile = new File(targetFile.getName() + ".tmp");
        oldFile = new File(targetFile.getName() + ".old");
        this.out = new FileOutputStream(writeFile);
    }

    @Override
    public void close() throws IOException {
        super.close();
        if (NIO_PATH_SUPPORTED) {
            handleMoveJava7();
        } else {
            handleMoveJava6();
        }
    }

    private void handleMoveJava6() throws IOException {
        /**
         * This works on some platforms (Linux), and is the best option.
         * People on other platforms (windows?) have less guarantee of atomicness.
         * We don't know whether this is actually why the rename fails though, because jdk6 kinda sucks.
         */
        if (!writeFile.renameTo(targetFile)) {
            oldFile.delete();
            if (!targetFile.exists() || targetFile.renameTo(oldFile)) {
                if (!writeFile.renameTo(targetFile)) {
                    throw new IOException("Unable to overwrite file with temporary file! New file is at " +
                            writeFile + ", old config at " + oldFile + ", target at " + targetFile);
                } else {
                    if (!oldFile.delete()) {
                        throw new IOException("Unable to delete old file " + oldFile);
                    }
                }
            }
        }

    }

    @IgnoreJRERequirement
    private void handleMoveJava7() throws IOException {
        Path writePath = writeFile.toPath();
        Path targetPath = targetFile.toPath();
        Files.move(writePath, targetPath, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy