001package co.codewizards.cloudstore.core.oio; 002 003import static co.codewizards.cloudstore.core.oio.OioFileFactory.*; 004import static java.util.Objects.*; 005 006import java.io.IOException; 007import java.util.LinkedList; 008 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012/** 013 * @author Sebastian Schefczyk 014 */ 015public class IoFileUtil { 016 017 private IoFileUtil() { } 018 019 private static final Logger logger = LoggerFactory.getLogger(IoFileUtil.class); 020 021 /** 022 * Discussion for best solution: 023 * http://stackoverflow.com/questions/617414/create-a-temporary-directory-in-java 024 */ 025 public static File createTempDirectory(final String prefix) throws IOException { 026 final String checkedPrefix = (prefix == null) ? "" : prefix; 027 final java.io.File javaIoTmpDir = new java.io.File(System.getProperty("java.io.tmpdir")); 028 final String baseName = checkedPrefix + System.currentTimeMillis(); 029 final int attempts = 1000; 030 for (int i = 0; i < attempts; i++) { 031 final java.io.File tmpDir = new java.io.File(javaIoTmpDir, baseName + i); 032 if (tmpDir.mkdir()) { 033 return createFile(tmpDir); 034 } 035 } 036 throw new IllegalStateException("Could not create a tmpDir in the directory '" + javaIoTmpDir + "'!"); 037 } 038 039 public static File createTempFile(final String prefix, final String suffix) throws IOException { 040 return createFile(java.io.File.createTempFile (prefix, suffix)); 041 } 042 043 public static File createTempFile(final String prefix, final String suffix, final File dir) throws IOException { 044 return createFile(java.io.File.createTempFile (prefix, suffix, castOrFail(dir).ioFile)); 045 } 046 047 public static java.io.File getIoFile(final File file) { 048 return castOrFail(file).ioFile; 049 } 050 051 public static File[] listRoots() { 052 final java.io.File[] roots = java.io.File.listRoots(); 053 requireNonNull(roots, "java.io.File.listRoots()"); 054 final File[] result = new File[roots.length]; 055 for (int i = 0; i < roots.length; i++) 056 result[i] = createFile(roots[i]); 057 058 return result; 059 } 060 061 public static IoFile castOrFail(final File file) { 062 if (file instanceof IoFile) 063 return (IoFile) file; 064 else 065 throw new IllegalArgumentException("Could not cast file: " 066 + file.getClass().getCanonicalName()); 067 } 068 069 static File[] convert(final java.io.File[] ioFilesListFiles) { 070 if (ioFilesListFiles == null) 071 return null; 072 final File[] listFiles = new File[ioFilesListFiles.length]; 073 for (int i = 0; i < ioFilesListFiles.length; i++) { 074 listFiles[i] = createFile(ioFilesListFiles[i]); 075 } 076 return listFiles; 077 } 078 079 /** 080 * Deletes recursively, but uses a stack instead of recursion, so it should be 081 * safe for very large file storages. 082 * <p/> 083 * If a symlink is found, only this link will be deleted, 084 * neither the symlinked directory nor its content (due to {@link File#delete()}). 085 * <p/> 086 * Discussion of best solution: http://stackoverflow.com/a/10337535/2287604 087 * 088 * @param dir The directory to delete recursively. 089 * @return True, if the param dir does not exist at the end. 090 */ 091 static boolean deleteRecursively(final File dir) { 092 final LinkedList<File> stack = new LinkedList<File>(); 093 stack.addFirst(dir); 094 while (!stack.isEmpty()) { 095 final File stackElement = stack.getFirst(); 096 final File[] currList = stackElement.listFiles(); 097 if (null != currList && currList.length > 0) { 098 for (final File curr : currList) { 099 try { 100 final boolean delete = curr.delete(); 101 /* Remark: delete() should succeed on empty directories, files and symlinks; 102 * only if was not successful, this *directory* should be 103 * pushed to the stack. So symlinked directories 104 * and its contents will not be deleted. 105 */ 106 if (!delete) { 107 stack.addFirst(curr); 108 } 109 } catch(final SecurityException e) { 110 logger.warn("Problem on delete of '{}'! ", curr, e.getMessage()); 111 } 112 } 113 } else { 114 if (stackElement != stack.removeFirst()) 115 throw new IllegalStateException("WTF?!"); 116 117 deleteOrLog(stackElement); 118 } 119 } 120 return !dir.exists(); 121 } 122 123 private static void deleteOrLog(final File file) { 124 try { 125 file.delete(); 126 } catch(final SecurityException e) { 127 logger.warn("Problem on delete of '{}'! ", file, e.getMessage()); 128 } 129 } 130 131 /** 132 * Directories will be created/deleted, files renamed. 133 * There is no rollback, if something happens during operation. 134 * @throws IOException 135 * If a directory itself could not be renamed, created or deleted. 136 * @throws SecurityException Not catched. If this happens, some files will 137 * be moved, some not, but no data loss should have happened. 138 */ 139 public static void moveRecursively(final File fromDir, final File toDir) throws IOException { 140 checkRenameDir(fromDir, toDir); 141 final boolean mkdir = toDir.mkdir(); 142 if (!mkdir) 143 throw new IOException("Could not create directory toDir, aborting move!"); 144 145 final File[] listFiles = fromDir.listFiles(); 146 for (final File file : listFiles) { 147 final File newFileName = newFileNameForRenameTo(fromDir, toDir, file); 148 if (file.isDirectory()) { 149 newFileName.mkdir(); 150 // remove this ; this could crash on big file collections! 151 moveRecursively(file, newFileName); 152 } else if (file.isFile()) { 153 file.renameTo(newFileName); 154 } 155 } 156 final boolean delete = fromDir.delete(); 157 if (!delete) 158 throw new IOException("Could not delete directory, which should be empty, aborting move! file=" + fromDir.getAbsolutePath()); 159 } 160 161 /** 162 * @param fromDir The from-dir of a move operation. 163 * @param toDir The destination dir of a move operation. 164 * @param current The current file, to compute the new name for. Must be inside of fromDir 165 * @return On moving from /a/fromDir/ to /a/toDir/, and current file is 166 * /a/fromDir/b/c, it will return /a/toDir/b/c 167 * @throws IOException 168 */ 169 public static File newFileNameForRenameTo(final File fromDir, final File toDir, final File current) throws IOException { 170 final String newParentDirName = toDir.getAbsolutePath() + OioFileFactory.FILE_SEPARATOR_CHAR + 171 IoFileRelativePathUtil.getRelativePath(fromDir.getAbsolutePath(), current.getAbsolutePath(), true, OioFileFactory.FILE_SEPARATOR_CHAR); 172 return createFile(newParentDirName); 173 } 174 175 /** Before starting a moveRecursively operation, which will make use of the 176 * File.renameTo method, it is useful to determine, whether this is possible 177 * or not. The reason: On a recursive rename, you will first move the inner 178 * content, and as last the root-directory. 179 */ 180 protected static void checkRenameDir(final File fromDir, final File toDir) throws IOException { 181 //first, lets check with a simple rename: 182 if (!fromDir.isDirectory()) 183 throw new IOException("fromDir must be directory, aborting move!"); 184 final File f = createFile(fromDir.getParentFile(), Long.toString(System.currentTimeMillis())); 185 final boolean mkdir = f.mkdir(); 186 if (!mkdir) 187 throw new IOException("Can not create mkdir, aborting move!"); 188 final boolean renameTo = f.renameTo(toDir); 189 if (!renameTo) 190 throw new IOException("Rename would not work, aborting move!"); 191 final boolean delete = toDir.delete(); 192 if (!delete) 193 throw new IOException("Could not delete, right after renaming!!!"); 194 } 195 196}