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;
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.
*
* @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;
}
/**
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;
*
* @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.
* 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);
+ }
+ }
}
/**
* 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);
+ }
}
/**
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");
+ }
+ }
}
--- /dev/null
+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();
+ }
+ }
+}
--- /dev/null
+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;
+ }
+ }
+}
addSeries(new SerialTest(args));
addSeries(new SerialServerTest(args));
addSeries(new StringUtilsTest(args));
+ addSeries(new TempFilesTest(args));
// TODO: test cache and downloader
Cache cache = null;