package com.bergerkiller.bukkit.common.config;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import com.bergerkiller.bukkit.common.utils.StreamUtil;
/**
* A {@link FileOutputStream} wrapper that writes to a temporary file prior to
* closing the stream and flushing the data to the official output file.
* It can be used to safely write data to disk without resulting
* in partially-written data when writing is interrupted.<br><br>
*
* Flushing this stream does <b>NOT</b> flush data to the output file, only to the
* temporary file. If the temporary file stream needs to be closed without replacing
* the original output file, use {@link #close(boolean) close(false)}.
*/
public class TempFileOutputStream extends OutputStream {
private final FileOutputStream baseStream;
private final File outputFile;
private final File tempFile;
private boolean closed;
/**
* Constructs a new TempFileOutputStream with the output file specified.
* The temporary file is the output file with <i>.tmp</i> appended.
*
* @param name of the file to eventually flush the data to
* @throws IOException if preparing the temporary file failed
*/
public TempFileOutputStream(String name) throws IOException {
this(new File(name), new File(name + ".tmp"));
}
/**
* Constructs a new TempFileOutputStream with the output file specified.
* The temporary file is the output file with <i>.tmp</i> appended.
*
* @param file to eventually flush the data to
* @throws IOException if preparing the temporary file failed
*/
public TempFileOutputStream(File file) throws IOException {
this(file, new File(file.getPath() + ".tmp"));
}
/**
* Constructs a new TempFileOutputStream with the output file and temporary file specified.
*
* @param outputFile to eventually flush the data to
* @param tempFile to write data to before flushing
* @throws IOException if preparing the temporary file failed
*/
public TempFileOutputStream(File outputFile, File tempFile) throws IOException {
this(outputFile, tempFile, StreamUtil.createOutputStream(tempFile));
}
/**
* Constructs a new TempFileOutputStream with the output file, temporary file and temporary base
* stream specified. The temporary base stream should be a {@link FileOutputStream} pointing to the
* <i>tempFile</i> parameter.
*
* @param outputFile to eventually flush the data to
* @param tempFile the tempBaseStream is accessing
* @param tempBaseStream to write data to before flushing
*/
public TempFileOutputStream(File outputFile, File tempFile, FileOutputStream tempBaseStream) {
super();
this.baseStream = tempBaseStream;
this.tempFile = tempFile;
this.outputFile = outputFile;
this.closed = false;
}
@Override
public void write(int b) throws IOException {
baseStream.write(b);
}
@Override
public void write(byte[] b) throws IOException {
baseStream.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
baseStream.write(b, off, len);
}
@Override
public void close() throws IOException {
close(true);
}
/**
* Closes the temporary file stream, writing any pending data to it. If completeTransfer is set,
* the output file is replaced with the temporary file. Typically, this parameter should specify
* whether previous writing was successful.
*
* @param completeTransfer - True to replace the output file, False to preserve it
* @throws IOException - if an I/O error occurs.
*/
public void close(boolean completeTransfer) throws IOException {
if (this.closed) {
return;
}
this.closed = true;
// Close the base streams
try {
super.close();
} finally {
baseStream.close();
}
if (!completeTransfer) {
return;
}
// Try to delete the output file first
if (!outputFile.delete() && outputFile.exists()) {
throw new IOException("Failed to delete existing output file prior to transfer");
}
// Move the temp file to the output file
if (!tempFile.renameTo(outputFile)) {
throw new IOException("Failed to transfer temporary file to output file");
}
}
}