From 51140fb7c70488c62193c0009a93bdbb2da487f9 Mon Sep 17 00:00:00 2001 From: Maschell Date: Thu, 6 Dec 2018 15:40:05 +0100 Subject: [PATCH] Write new files from a Stream at first to a .tmp file. This avoids fragmentation. --- .../mas/wiiu/jnus/utils/CheckedFunction.java | 8 ++ src/de/mas/wiiu/jnus/utils/FileUtils.java | 39 +++++- .../jnus/utils/RandomFileOutputStream.java | 111 ++++++++++++++++++ 3 files changed, 152 insertions(+), 6 deletions(-) create mode 100644 src/de/mas/wiiu/jnus/utils/CheckedFunction.java create mode 100644 src/de/mas/wiiu/jnus/utils/RandomFileOutputStream.java diff --git a/src/de/mas/wiiu/jnus/utils/CheckedFunction.java b/src/de/mas/wiiu/jnus/utils/CheckedFunction.java new file mode 100644 index 0000000..763fbd6 --- /dev/null +++ b/src/de/mas/wiiu/jnus/utils/CheckedFunction.java @@ -0,0 +1,8 @@ +package de.mas.wiiu.jnus.utils; + +import java.io.IOException; + +@FunctionalInterface +public interface CheckedFunction { + void apply(T t) throws IOException; +} \ No newline at end of file diff --git a/src/de/mas/wiiu/jnus/utils/FileUtils.java b/src/de/mas/wiiu/jnus/utils/FileUtils.java index 742a6bd..1485f49 100644 --- a/src/de/mas/wiiu/jnus/utils/FileUtils.java +++ b/src/de/mas/wiiu/jnus/utils/FileUtils.java @@ -20,6 +20,8 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; import lombok.NonNull; @@ -55,16 +57,41 @@ public final class FileUtils { return true; } + public static void saveInputStreamToFile(@NonNull File outputFile, InputStream inputStream, long filesize) throws IOException { + FileAsOutputStreamWrapper(outputFile, filesize, outputStream -> StreamUtils.saveInputStreamToOutputStream(inputStream, outputStream, filesize)); + } + /** - * Saves a byte array to a file (and overwrite it if its already exists) DOES NOT IF THE PATH/FILE EXIST OR IS IT EVEN A FILE + * Allows to write into a target file as OutputStream with some extras. The provided OutputStream already has the needed memory allocated. This results in a + * non-fragmented file. * - * @param output - * @param inputStream + * @param outputFile + * @param filesize + * @param action * @throws IOException */ - public static void saveInputStreamToFile(@NonNull File output, InputStream inputStream, long filesize) throws IOException { - FileOutputStream out = new FileOutputStream(output); - StreamUtils.saveInputStreamToOutputStream(inputStream, out, filesize); + public static void FileAsOutputStreamWrapper(@NonNull File outputFile, long filesize, CheckedFunction action) throws IOException { + // Create a new temp file which already has the target filesize allocated. + String tempFilePath = outputFile.getAbsolutePath() + "." + outputFile.getAbsolutePath().hashCode() + ".part"; + File tempFile = new File(tempFilePath); + if (tempFile.exists()) { + tempFile.delete(); + } + + tempFile.createNewFile(); + RandomAccessFile outStream = new RandomAccessFile(tempFilePath, "rw"); + outStream.setLength(filesize); + outStream.seek(0L); + + action.apply(new RandomFileOutputStream(outStream)); + + outStream.close(); + + // Rename temp file. + if (outputFile.exists()) { + outputFile.delete(); + } + tempFile.renameTo(outputFile); } } diff --git a/src/de/mas/wiiu/jnus/utils/RandomFileOutputStream.java b/src/de/mas/wiiu/jnus/utils/RandomFileOutputStream.java new file mode 100644 index 0000000..9e18bbb --- /dev/null +++ b/src/de/mas/wiiu/jnus/utils/RandomFileOutputStream.java @@ -0,0 +1,111 @@ +package de.mas.wiiu.jnus.utils; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; + +public class RandomFileOutputStream extends OutputStream { + + // ***************************************************************************** + // INSTANCE PROPERTIES + // ***************************************************************************** + + protected RandomAccessFile randomFile; // the random file to write to + protected boolean sync; // whether to synchronize every write + + // ***************************************************************************** + // INSTANCE CONSTRUCTION/INITIALIZATON/FINALIZATION, OPEN/CLOSE + // ***************************************************************************** + + public RandomFileOutputStream(String fnm) throws IOException { + this(fnm, false); + } + + public RandomFileOutputStream(String fnm, boolean syn) throws IOException { + this(new File(fnm), syn); + } + + public RandomFileOutputStream(File fil) throws IOException { + this(fil, false); + } + + public RandomFileOutputStream(RandomAccessFile ran) throws IOException { + randomFile = ran; + sync = false; + } + + public RandomFileOutputStream(File fil, boolean syn) throws IOException { + super(); + + File par; // parent file + + fil = fil.getAbsoluteFile(); + if ((par = fil.getParentFile()) != null) { + Utils.createDir(par.getAbsolutePath()); + } + randomFile = new RandomAccessFile(fil, "rw"); + sync = syn; + } + + // ***************************************************************************** + // INSTANCE METHODS - OUTPUT STREAM IMPLEMENTATION + // ***************************************************************************** + + public void write(int val) throws IOException { + randomFile.write(val); + if (sync) { + randomFile.getFD().sync(); + } + } + + public void write(byte[] val) throws IOException { + randomFile.write(val); + if (sync) { + randomFile.getFD().sync(); + } + } + + public void write(byte[] val, int off, int len) throws IOException { + randomFile.write(val, off, len); + if (sync) { + randomFile.getFD().sync(); + } + } + + public void flush() throws IOException { + if (sync) { + randomFile.getFD().sync(); + } + } + + public void close() throws IOException { + randomFile.close(); + } + + // ***************************************************************************** + // INSTANCE METHODS - RANDOM ACCESS EXTENSIONS + // ***************************************************************************** + + public long getFilePointer() throws IOException { + return randomFile.getFilePointer(); + } + + public void setFilePointer(long pos) throws IOException { + randomFile.seek(pos); + } + + public long getFileSize() throws IOException { + return randomFile.length(); + } + + public void setFileSize(long len) throws IOException { + randomFile.setLength(len); + } + + public FileDescriptor getFD() throws IOException { + return randomFile.getFD(); + } + +} // END PUBLIC CLASS \ No newline at end of file