package co.codewizards.cloudstore.core.oio; import static co.codewizards.cloudstore.core.oio.OioFileFactory.*; import static co.codewizards.cloudstore.core.util.AssertUtil.*; import java.io.IOException; import java.util.LinkedList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Sebastian Schefczyk */ public class IoFileUtil { private IoFileUtil() { } private static final Logger logger = LoggerFactory.getLogger(IoFileUtil.class); /** * Discussion for best solution: * http://stackoverflow.com/questions/617414/create-a-temporary-directory-in-java */ public static File createTempDirectory(final String prefix) throws IOException { final String checkedPrefix = (prefix == null) ? "" : prefix; final java.io.File javaIoTmpDir = new java.io.File(System.getProperty("java.io.tmpdir")); final String baseName = checkedPrefix + System.currentTimeMillis(); final int attempts = 1000; for (int i = 0; i < attempts; i++) { final java.io.File tmpDir = new java.io.File(javaIoTmpDir, baseName + i); if (tmpDir.mkdir()) { return createFile(tmpDir); } } throw new IllegalStateException("Could not create a tmpDir in the directory '" + javaIoTmpDir + "'!"); } public static File createTempFile(final String prefix, final String suffix) throws IOException { return createFile(java.io.File.createTempFile (prefix, suffix)); } public static File createTempFile(final String prefix, final String suffix, final File dir) throws IOException { return createFile(java.io.File.createTempFile (prefix, suffix, castOrFail(dir).ioFile)); } public static java.io.File getIoFile(final File file) { return castOrFail(file).ioFile; } public static File[] listRoots() { final java.io.File[] roots = java.io.File.listRoots(); assertNotNull(roots, "java.io.File.listRoots()"); final File[] result = new File[roots.length]; for (int i = 0; i < roots.length; i++) result[i] = createFile(roots[i]); return result; } public static IoFile castOrFail(final File file) { if (file instanceof IoFile) return (IoFile) file; else throw new IllegalArgumentException("Could not cast file: " + file.getClass().getCanonicalName()); } static File[] convert(final java.io.File[] ioFilesListFiles) { if (ioFilesListFiles == null) return null; final File[] listFiles = new File[ioFilesListFiles.length]; for (int i = 0; i < ioFilesListFiles.length; i++) { listFiles[i] = createFile(ioFilesListFiles[i]); } return listFiles; } /** * Deletes recursively, but uses a stack instead of recursion, so it should be * safe for very large file storages. * <p/> * If a symlink is found, only this link will be deleted, * neither the symlinked directory nor its content (due to {@link File#delete()}). * <p/> * Discussion of best solution: http://stackoverflow.com/a/10337535/2287604 * * @param dir The directory to delete recursively. * @return True, if the param dir does not exist at the end. */ static boolean deleteRecursively(final File dir) { final LinkedList<File> stack = new LinkedList<File>(); stack.addFirst(dir); while (!stack.isEmpty()) { final File stackElement = stack.getFirst(); final File[] currList = stackElement.listFiles(); if (null != currList && currList.length > 0) { for (final File curr : currList) { try { final boolean delete = curr.delete(); /* Remark: delete() should succeed on empty directories, files and symlinks; * only if was not successful, this *directory* should be * pushed to the stack. So symlinked directories * and its contents will not be deleted. */ if (!delete) { stack.addFirst(curr); } } catch(final SecurityException e) { logger.warn("Problem on delete of '{}'! ", curr, e.getMessage()); } } } else { if (stackElement != stack.removeFirst()) throw new IllegalStateException("WTF?!"); deleteOrLog(stackElement); } } return !dir.exists(); } private static void deleteOrLog(final File file) { try { file.delete(); } catch(final SecurityException e) { logger.warn("Problem on delete of '{}'! ", file, e.getMessage()); } } /** * Directories will be created/deleted, files renamed. * There is no rollback, if something happens during operation. * @throws IOException * If a directory itself could not be renamed, created or deleted. * @throws SecurityException Not catched. If this happens, some files will * be moved, some not, but no data loss should have happened. */ public static void moveRecursively(final File fromDir, final File toDir) throws IOException { checkRenameDir(fromDir, toDir); final boolean mkdir = toDir.mkdir(); if (!mkdir) throw new IOException("Could not create directory toDir, aborting move!"); final File[] listFiles = fromDir.listFiles(); for (final File file : listFiles) { final File newFileName = newFileNameForRenameTo(fromDir, toDir, file); if (file.isDirectory()) { newFileName.mkdir(); // remove this ; this could crash on big file collections! moveRecursively(file, newFileName); } else if (file.isFile()) { file.renameTo(newFileName); } } final boolean delete = fromDir.delete(); if (!delete) throw new IOException("Could not delete directory, which should be empty, aborting move! file=" + fromDir.getAbsolutePath()); } /** * @param fromDir The from-dir of a move operation. * @param toDir The destination dir of a move operation. * @param current The current file, to compute the new name for. Must be inside of fromDir * @return On moving from /a/fromDir/ to /a/toDir/, and current file is * /a/fromDir/b/c, it will return /a/toDir/b/c * @throws IOException */ public static File newFileNameForRenameTo(final File fromDir, final File toDir, final File current) throws IOException { final String newParentDirName = toDir.getAbsolutePath() + OioFileFactory.FILE_SEPARATOR_CHAR + IoFileRelativePathUtil.getRelativePath(fromDir.getAbsolutePath(), current.getAbsolutePath(), true, OioFileFactory.FILE_SEPARATOR_CHAR); return createFile(newParentDirName); } /** Before starting a moveRecursively operation, which will make use of the * File.renameTo method, it is useful to determine, whether this is possible * or not. The reason: On a recursive rename, you will first move the inner * content, and as last the root-directory. */ protected static void checkRenameDir(final File fromDir, final File toDir) throws IOException { //first, lets check with a simple rename: if (!fromDir.isDirectory()) throw new IOException("fromDir must be directory, aborting move!"); final File f = createFile(fromDir.getParentFile(), Long.toString(System.currentTimeMillis())); final boolean mkdir = f.mkdir(); if (!mkdir) throw new IOException("Can not create mkdir, aborting move!"); final boolean renameTo = f.renameTo(toDir); if (!renameTo) throw new IOException("Rename would not work, aborting move!"); final boolean delete = toDir.delete(); if (!delete) throw new IOException("Could not delete, right after renaming!!!"); } }