From da0bc4a662c3a808fdb182d3f992e93915e41577 Mon Sep 17 00:00:00 2001 From: Gustavo Martin Morcuende Date: Thu, 5 Nov 2015 13:40:21 +0100 Subject: [PATCH] FileSafeWriter with example --- .../src/de/example/fsync/FileSafeWriter.java | 136 +++++++++++++++++++++ .../src/de/example/fsync/FileSafeWriterMain.java | 81 ++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 Allgemeines/FileSafeWriter/src/de/example/fsync/FileSafeWriter.java create mode 100644 Allgemeines/FileSafeWriter/src/de/example/fsync/FileSafeWriterMain.java diff --git a/Allgemeines/FileSafeWriter/src/de/example/fsync/FileSafeWriter.java b/Allgemeines/FileSafeWriter/src/de/example/fsync/FileSafeWriter.java new file mode 100644 index 0000000..81a33bb --- /dev/null +++ b/Allgemeines/FileSafeWriter/src/de/example/fsync/FileSafeWriter.java @@ -0,0 +1,136 @@ +package de.example.fsync; + +import java.io.Closeable; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * If you want to make sure what you are writing to disk is not going to get + * lost you should use this class. + * + *

Hopefully you are already closing your output streams in a finally block + * (if you aren't, you don't deserve my respect) and in that case you could be + * wondering what this class is for. From Linux's close(2): + * + *

A successful close does not guarantee that the data has been successfully + * saved to disk, as the kernel defers writes. It is not common for a file system + * to flush the buffers when the stream is closed. If you need to be sure that + * the data is physically stored use fsync(2). (It will depend on the disk hardware + * at this point.) + * + *

See also: {@link http://thunk.org/tytso/blog/2009/03/15/dont-fear-the-fsync/} + * + */ +public class FileSafeWriter { + private final SafeWriter safeWriter; + + /** + * This interface must be used to implement the user's methods in charge of + * writing to and closing output stream. + */ + public interface SafeWriter extends Closeable { + + public void doWrite(final OutputStream out) throws IOException; + } + + /** + * Creates object with the user's implementation in charge of writing to + * output stream. + * + * @param safeWriter user's implementation in charge of writing to output + * stream + */ + public FileSafeWriter(final SafeWriter safeWriter) { + this.safeWriter = safeWriter; + } + + /** + * Writes data to file in a safe way. + * + *

This write is atomic (as long as you are not using FAT16, FAT32 or NFS) + * in the sense that it is first written to a temporary file which is then + * renamed to the final name. + * + * @param file destination file + * @throws FileNotFoundException if the file exists but is a directory rather + * than a regular file, does not exist but cannot be created, or cannot be + * opened for any other reason + * @throws IOException if an I/O error occurs + */ + public void writeFile(final File file) + throws FileNotFoundException, IOException { + + final File tempFile = this.writeTempFile(file); + + this.renameFile(tempFile, file); + } + + /** + * Writes data to temporary file. + * + *

+ * Note that the name for the temporary file is constructed by appending + * random characters to the name (just its name, not the whole path) of + * file. + *

+ * + * @param file destination file + * @return temporary file with the temporarily stored data + * @throws FileNotFoundException if the file exists but is a directory rather + * than a regular file, does not exist but cannot be created, or cannot be + * opened for any other reason + * @throws IOException if an I/O error occurs + */ + private File writeTempFile(final File file) + throws FileNotFoundException, IOException { + + final File tempFile = File.createTempFile(file.getName(), ".tmp", null); + final FileOutputStream tempFileStream = new FileOutputStream(tempFile, false); + try { + safeWriter.doWrite(tempFileStream); + + /* We want to sync the newly written file to ensure the data is on disk + * when we rename over the destination. Otherwise if we get a system + * crash we can lose both the new and the old file on some filesystems. + * (I.E. those that don't guarantee the data is written to the disk + * before the metadata.) + */ + tempFileStream.flush(); + tempFileStream.getFD().sync(); + } finally { + // Override exception (if there is one) Hopefully it will not be a problem. + try { + safeWriter.close(); + } finally { + tempFileStream.close(); + } + } + + return tempFile; + } + + /** + * This method renames file and tries to clear everything in case of error. + * + *

Rename file should be atomic (if you are not using FAT16, FAT32 or NFS) + * + *

See also: {@link http://rcrowley.org/2010/01/06/things-unix-can-do-atomically.html} + * + * @param fromFile destination file + * @param toFile target file + * @throws IOException if there was some error + */ + private void renameFile(final File fromFile, final File toFile) throws IOException { + if (!fromFile.renameTo(toFile)) { + if (!fromFile.delete()) { + System.out.println("[FileSafeWriter] Could not remove temporary file: " + + fromFile.getAbsolutePath()); + } + throw new IOException("[FileSafeWriter] Could not move from " + + fromFile.getAbsolutePath() + " to " + toFile.getAbsolutePath()); + } + } +} diff --git a/Allgemeines/FileSafeWriter/src/de/example/fsync/FileSafeWriterMain.java b/Allgemeines/FileSafeWriter/src/de/example/fsync/FileSafeWriterMain.java new file mode 100644 index 0000000..47d4be9 --- /dev/null +++ b/Allgemeines/FileSafeWriter/src/de/example/fsync/FileSafeWriterMain.java @@ -0,0 +1,81 @@ +package de.example.fsync; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +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.UnsupportedEncodingException; +import java.io.Writer; + + +public class FileSafeWriterMain { + private static final File exampleFile = new File("/tmp/fileSafeWriterExample"); + + public static void main(String[] args) throws FileNotFoundException, IOException { + + final FileSafeWriter safeFileWriter = new FileSafeWriter(new ExampleSafeWriter(9999)); + safeFileWriter.writeFile(exampleFile); + + System.out.println("Result: " + getPersistedData()); + } + + private static Integer getPersistedData() + throws FileNotFoundException, UnsupportedEncodingException, IOException { + InputStream inputStream = null; + Reader readerStream = null; + BufferedReader reader = null; + try { + inputStream = new FileInputStream(exampleFile); + readerStream = new InputStreamReader(inputStream, "UTF-8"); + reader = new BufferedReader(readerStream); + return Integer.valueOf(reader.readLine()); + } finally { + if (reader != null) { + reader.close(); + } + if (readerStream != null) { + readerStream.close(); + } + if (readerStream != null) { + readerStream.close(); + } + } + } + + + private static class ExampleSafeWriter implements FileSafeWriter.SafeWriter { + private final Integer number; + private Writer writerStream = null; + private BufferedWriter buffer = null; + + private ExampleSafeWriter(final Integer number) { + this.number = number; + } + + @Override + public void doWrite(final OutputStream out) throws IOException { + writerStream = new OutputStreamWriter(out, "UTF-8"); + buffer = new BufferedWriter(writerStream); + buffer.write(String.valueOf(number)); buffer.newLine(); + } + + @Override + public void close() throws IOException { + if (buffer != null) { + buffer.close(); + } + + if (writerStream != null) { + writerStream.close(); + } + } + } + +} -- 2.1.4