Skip to content

Commit

Permalink
Add util for atomic file writing
Browse files Browse the repository at this point in the history
Given a particular OS or drive combination, atomic rename may not be available,
but we do the overall write as atomically as possible. The method takes a
function for writing the file contents, rather than a string, so that we can
support streaming to the file, which is more efficient.
  • Loading branch information
garfieldnate committed Jan 8, 2025
1 parent 05eaadb commit e369459
Showing 1 changed file with 49 additions and 0 deletions.
49 changes: 49 additions & 0 deletions src/main/java/edu/umich/soar/visualsoar/files/Util.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package edu.umich.soar.visualsoar.files;

import java.io.FileOutputStream;
import java.io.OutputStream;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.logging.Logger;

import static java.nio.file.StandardCopyOption.ATOMIC_MOVE;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

public class Util {
private static final Logger LOGGER = Logger.getLogger(Util.class.getName());

@FunctionalInterface
public interface Writer {
void write(OutputStream out);
}

/**
* Write to the destination file from {@code writer} as atomically as possible.
*/
public void Save(Path destination, Writer writer) throws java.io.IOException {
// Write to a temp file first, then make the final changes via a rename,
// which can often be done atomically. This prevents issues such as overwriting a file with a
// incomplete data, as could be the case if we hit an IO exception in the middle of writing the
// file (especially relevant for folks using network drives!)
String tempFilename = destination.toAbsolutePath() + ".temp";
Path tempPath = Paths.get(tempFilename);

try (FileOutputStream output = new FileOutputStream(tempPath.toFile())) {
writer.write(output);
}

try {
Files.move(tempPath, destination, REPLACE_EXISTING, ATOMIC_MOVE);
} catch (AtomicMoveNotSupportedException e) {
LOGGER.warning(
"Cannot write "
+ destination
+ " atomically ("
+ e.getMessage()
+ "); falling back to non-atomic write");
Files.move(tempPath, destination, REPLACE_EXISTING);
}
}
}

0 comments on commit e369459

Please sign in to comment.