New TempFiles and Image now uses it instead of mem
authorNiki Roo <niki@nikiroo.be>
Wed, 10 Jan 2018 09:49:04 +0000 (10:49 +0100)
committerNiki Roo <niki@nikiroo.be>
Wed, 10 Jan 2018 09:49:04 +0000 (10:49 +0100)
src/be/nikiroo/utils/IOUtils.java
src/be/nikiroo/utils/Image.java
src/be/nikiroo/utils/TempFiles.java [new file with mode: 0644]
src/be/nikiroo/utils/test/TempFilesTest.java [new file with mode: 0644]
src/be/nikiroo/utils/test/Test.java

index f857850dea11db8d4185fd21ad226b86380cc034..bf0686babca9e7addfbbd810a7a8e4d5fd66756d 100644 (file)
@@ -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.
+        * <p>
+        * 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<File> 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.
+        * <p>
+        * 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<File> deltree(File target, List<File> errorAcc) {
+               if (errorAcc == null) {
+                       errorAcc = new ArrayList<File>();
+               }
+
                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;
        }
 
        /**
index 58c0e1019204ea2e3a35647754164b57653e7684..3a89d29d4887e0121107871af39d660c4ca041c3 100644 (file)
@@ -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.
-        * <p>
-        * This is the actual data, not a copy, so any change made here will be
-        * reflected into the {@link Image} and vice-versa.
+        * <b>Read</b> 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.
+        * <p>
+        * Note that even if you don't, the program will still <b>try</b> 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 (file)
index 0000000..a16e9e4
--- /dev/null
@@ -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.
+        * <p>
+        * The whole repository will be deleted on close (if you fail to call it,
+        * the program will <b>try</b> to all it on JVM termination).
+        * 
+        * @param name
+        *            the instance name (will be <b>part</b> 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 <b>part</b> 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.
+        * <p>
+        * Note that creating 2 temporary directories with the same name will result
+        * in two <b>different</b> 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 (file)
index 0000000..905ca4d
--- /dev/null
@@ -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;
+               }
+       }
+}
index b88be7c64095c19e79e4b262f3573f8fe325728a..dd6bf611792c302fc239082c8557583fff111271 100644 (file)
@@ -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;