From: Niki Roo Date: Wed, 10 Jan 2018 09:49:04 +0000 (+0100) Subject: New TempFiles and Image now uses it instead of mem X-Git-Tag: nikiroo-utils-4.1.0~7 X-Git-Url: https://git.nikiroo.be/?a=commitdiff_plain;h=59864f77f0b8d5a57f479152a3131093544af6b2;p=nikiroo-utils.git New TempFiles and Image now uses it instead of mem --- diff --git a/src/be/nikiroo/utils/IOUtils.java b/src/be/nikiroo/utils/IOUtils.java index f857850..bf0686b 100644 --- a/src/be/nikiroo/utils/IOUtils.java +++ b/src/be/nikiroo/utils/IOUtils.java @@ -10,6 +10,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -209,6 +211,41 @@ public class IOUtils { return builder.toString(); } + /** + * Recursively delete the given {@link File}, which may of course also be a + * directory. + *

+ * Will either silently continue or throw an exception in case of error, + * depending upon the parameters. + * + * @param target + * the target to delete + * @param exception + * TRUE to throw an {@link IOException} in case of error, FALSE + * to silently continue + * + * @return TRUE if all files were deleted, FALSE if an error occurred + * + * @throws IOException + * if an error occurred and the parameters allow an exception to + * be thrown + */ + public static boolean deltree(File target, boolean exception) + throws IOException { + List list = deltree(target, null); + if (exception && !list.isEmpty()) { + String slist = ""; + for (File file : list) { + slist += "\n" + file.getPath(); + } + + throw new IOException("Cannot delete all the files from: <" // + + target + ">:" + slist); + } + + return list.isEmpty(); + } + /** * Recursively delete the given {@link File}, which may of course also be a * directory. @@ -217,18 +254,44 @@ public class IOUtils { * * @param target * the target to delete + * + * @return TRUE if all files were deleted, FALSE if an error occurred + */ + public static boolean deltree(File target) { + return deltree(target, null).isEmpty(); + } + + /** + * Recursively delete the given {@link File}, which may of course also be a + * directory. + *

+ * Will collect all {@link File} that cannot be deleted in the given + * accumulator. + * + * @param target + * the target to delete + * @param errorAcc + * the accumulator to use for errors, or NULL to create a new one + * + * @return the errors accumulator */ - public static void deltree(File target) { + public static List deltree(File target, List errorAcc) { + if (errorAcc == null) { + errorAcc = new ArrayList(); + } + File[] files = target.listFiles(); if (files != null) { for (File file : files) { - deltree(file); + errorAcc = deltree(file, errorAcc); } } if (!target.delete()) { - System.err.println("Cannot delete: " + target.getAbsolutePath()); + errorAcc.add(target); } + + return errorAcc; } /** diff --git a/src/be/nikiroo/utils/Image.java b/src/be/nikiroo/utils/Image.java index 58c0e10..3a89d29 100644 --- a/src/be/nikiroo/utils/Image.java +++ b/src/be/nikiroo/utils/Image.java @@ -1,5 +1,9 @@ package be.nikiroo.utils; +import java.io.ByteArrayInputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; @@ -8,8 +12,12 @@ import java.io.InputStream; * * @author niki */ -public class Image { - private byte[] data; +public class Image implements Closeable { + static private TempFiles tmpRepository; + static private long count = 0; + static private Object lock = new Object(); + + private File data; /** * Do not use -- for serialisation purposes only. @@ -25,7 +33,19 @@ public class Image { * the data */ public Image(byte[] data) { - this.data = data; + ByteArrayInputStream in = new ByteArrayInputStream(data); + try { + this.data = getTemporaryFile(); + IOUtils.write(in, this.data); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + try { + in.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } } /** @@ -51,19 +71,26 @@ public class Image { * in case of I/O error */ public Image(InputStream in) throws IOException { - this.data = IOUtils.toByteArray(in); + data = getTemporaryFile(); + IOUtils.write(in, data); } /** - * The actual image data. - *

- * This is the actual data, not a copy, so any change made here will be - * reflected into the {@link Image} and vice-versa. + * Read the actual image data, as a byte array. * * @return the image data */ public byte[] getData() { - return data; + try { + FileInputStream in = new FileInputStream(data); + try { + return IOUtils.toByteArray(in); + } finally { + in.close(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } } /** @@ -75,4 +102,54 @@ public class Image { public String toBase64() { return new String(Base64.encodeBytes(getData())); } + + /** + * Closing the {@link Image} will delete the associated temporary file on + * disk. + *

+ * Note that even if you don't, the program will still try to delete + * all the temporary files at JVM termination. + */ + @Override + public void close() throws IOException { + data.delete(); + synchronized (lock) { + count--; + if (count <= 0) { + count = 0; + tmpRepository.close(); + tmpRepository = null; + } + } + } + + @Override + protected void finalize() throws Throwable { + try { + close(); + } finally { + super.finalize(); + } + } + + /** + * Return a newly created temporary file to work on. + * + * @return the file + * + * @throws IOException + * in case of I/O error + */ + private File getTemporaryFile() throws IOException { + synchronized (lock) { + if (tmpRepository == null) { + tmpRepository = new TempFiles("images"); + count = 0; + } + + count++; + + return tmpRepository.createTempFile("image"); + } + } } diff --git a/src/be/nikiroo/utils/TempFiles.java b/src/be/nikiroo/utils/TempFiles.java new file mode 100644 index 0000000..a16e9e4 --- /dev/null +++ b/src/be/nikiroo/utils/TempFiles.java @@ -0,0 +1,143 @@ +package be.nikiroo.utils; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; + +/** + * A small utility class to generate auto-delete temporary files in a + * centralised location. + * + * @author niki + */ +public class TempFiles implements Closeable { + protected File root; + + /** + * Create a new {@link TempFiles} -- each instance is separate and have a + * dedicated sub-directory in a shared temporary root. + *

+ * The whole repository will be deleted on close (if you fail to call it, + * the program will try to all it on JVM termination). + * + * @param name + * the instance name (will be part of the final directory + * name) + * + * @throws IOException + * in case of I/O error + */ + public TempFiles(String name) throws IOException { + root = File.createTempFile(".temp", ""); + IOUtils.deltree(root, true); + + root = new File(root.getParentFile(), ".temp"); + root.mkdir(); + if (!root.exists()) { + throw new IOException("Cannot create root directory: " + root); + } + + root.deleteOnExit(); + + root = createTempFile(name); + IOUtils.deltree(root, true); + + root.mkdir(); + if (!root.exists()) { + throw new IOException("Cannot create root subdirectory: " + root); + } + } + + /** + * Create an auto-delete temporary file. + * + * @param name + * a base for the final filename (only a part of said + * filename) + * + * @return the newly created file + * + * @throws IOException + * in case of I/O errors + */ + public synchronized File createTempFile(String name) throws IOException { + name += "_"; + while (name.length() < 3) { + name += "_"; + } + + while (true) { + File tmp = File.createTempFile(name, ""); + IOUtils.deltree(tmp, true); + + File test = new File(root, tmp.getName()); + if (!test.exists()) { + test.createNewFile(); + if (!test.exists()) { + throw new IOException("Cannot create temporary file: " + + test); + } + + test.deleteOnExit(); + return test; + } + } + } + + /** + * Create an auto-delete temporary directory. + *

+ * Note that creating 2 temporary directories with the same name will result + * in two different directories, even if the final name is the same + * (the absolute path will be different). + * + * @param name + * the actual directory name (not path) + * + * @return the newly created file + * + * @throws IOException + * in case of I/O errors, or if the name was a path instead of a + * name + */ + public synchronized File createTempDir(String name) throws IOException { + File localRoot = createTempFile(name); + IOUtils.deltree(localRoot, true); + + localRoot.mkdir(); + if (!localRoot.exists()) { + throw new IOException("Cannot create subdirectory: " + localRoot); + } + + File dir = new File(localRoot, name); + if (!dir.getName().equals(name)) { + throw new IOException( + "Cannot create temporary directory with a path, only names are allowed: " + + dir); + } + + dir.mkdir(); + dir.deleteOnExit(); + + if (!dir.exists()) { + throw new IOException("Cannot create subdirectory: " + dir); + } + + return dir; + } + + @Override + public synchronized void close() throws IOException { + IOUtils.deltree(root); // NO exception here + root.getParentFile().delete(); // only if empty + } + + @Override + protected void finalize() throws Throwable { + try { + close(); + } finally { + super.finalize(); + } + } +} diff --git a/src/be/nikiroo/utils/test/TempFilesTest.java b/src/be/nikiroo/utils/test/TempFilesTest.java new file mode 100644 index 0000000..905ca4d --- /dev/null +++ b/src/be/nikiroo/utils/test/TempFilesTest.java @@ -0,0 +1,97 @@ +package be.nikiroo.utils.test; + +import java.io.File; +import java.io.IOException; + +import be.nikiroo.utils.TempFiles; + +class TempFilesTest extends TestLauncher { + public TempFilesTest(String[] args) { + super("TempFiles", args); + + addTest(new TestCase("Name is correct") { + @Override + public void test() throws Exception { + RootTempFiles files = new RootTempFiles("testy"); + try { + assertEquals("The root was not created", true, files + .getRoot().exists()); + assertEquals( + "The root is not prefixed with the expected name", + true, files.getRoot().getName().startsWith("testy")); + + } finally { + files.close(); + } + } + }); + + addTest(new TestCase("Clean after itself no use") { + @Override + public void test() throws Exception { + RootTempFiles files = new RootTempFiles("testy2"); + try { + assertEquals("The root was not created", true, files + .getRoot().exists()); + } finally { + files.close(); + assertEquals("The root was not deleted", false, files + .getRoot().exists()); + } + } + }); + + addTest(new TestCase("Clean after itself after usage") { + @Override + public void test() throws Exception { + RootTempFiles files = new RootTempFiles("testy3"); + try { + assertEquals("The root was not created", true, files + .getRoot().exists()); + files.createTempFile("test"); + } finally { + files.close(); + assertEquals("The root was not deleted", false, files + .getRoot().exists()); + assertEquals("The main root in /tmp was not deleted", + false, files.getRoot().getParentFile().exists()); + } + } + }); + + addTest(new TestCase("Temporary directories") { + @Override + public void test() throws Exception { + RootTempFiles files = new RootTempFiles("testy4"); + File file = null; + try { + File dir = files.createTempDir("test"); + file = new File(dir, "fanfan"); + file.createNewFile(); + assertEquals( + "Cannot create a file in a temporary directory", + true, file.exists()); + } finally { + files.close(); + if (file != null) { + assertEquals( + "A file created in a temporary directory should be deleted on close", + false, file.exists()); + } + assertEquals("The root was not deleted", false, files + .getRoot().exists()); + } + } + }); + } + + private class RootTempFiles extends TempFiles { + public RootTempFiles(String name) throws IOException { + super(name); + } + + public File getRoot() { + return root; + } + } +} diff --git a/src/be/nikiroo/utils/test/Test.java b/src/be/nikiroo/utils/test/Test.java index b88be7c..dd6bf61 100644 --- a/src/be/nikiroo/utils/test/Test.java +++ b/src/be/nikiroo/utils/test/Test.java @@ -26,6 +26,7 @@ public class Test extends TestLauncher { addSeries(new SerialTest(args)); addSeries(new SerialServerTest(args)); addSeries(new StringUtilsTest(args)); + addSeries(new TempFilesTest(args)); // TODO: test cache and downloader Cache cache = null;