/* ************************************************************************ # # DivConq # # http://divconq.com/ # # Copyright: # Copyright 2014 eTimeline, LLC. All rights reserved. # # License: # See the license.txt file in the project's top-level directory for details. # # Authors: # * Andy White # ************************************************************************ */ package divconq.util; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileVisitOption; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.EnumSet; import java.util.Random; import java.util.UUID; import java.util.function.Predicate; import java.util.stream.Stream; import divconq.filestore.CommonPath; import divconq.io.LineIterator; import divconq.lang.op.OperationResult; // see Hub, it clears the temp files public class FileUtil { // only use this in tests or completely safe functions, this is not meant to provide secure random services to entire framework // SecureRandom is way too slow for what we need here static public final Random testrnd = new Random(); static public String randomFilename() { return UUID.randomUUID().toString().replace("-", ""); } static public String randomFilename(String ext) { return UUID.randomUUID().toString().replace("-", "") + "." + ext; } static public File allocateTemp() { return FileUtil.allocateTemp("tmp"); } static public File allocateTemp(String ext) { File temps = new File("./temp"); temps.mkdirs(); String fname = FileUtil.randomFilename(ext); return new File(temps, fname); } static public File allocateTempFolder() { File temps = new File("./temp/" + FileUtil.randomFilename()); temps.mkdirs(); return temps; } static public Path allocateTempFolder2() { try { Path temps = Paths.get("./temp/" + FileUtil.randomFilename()); Files.createDirectories(temps); return temps; } catch (IOException x) { } return null; } static public File pathTempFolder(String name) { File temps = new File("./temp/" + name); return temps; } static public void cleanupTemp() { File temps = new File("./temp"); if (!temps.exists()) return; for(File next : temps.listFiles()) { if (next.isDirectory()) continue; long time = System.currentTimeMillis(); long modified = next.lastModified(); if(modified + (60 * 60 * 1000) > time) // wait an hour continue; next.delete(); } } static public String getFileExtension(Path p) { String fname = p.getFileName().toString(); if (fname == null) return null; int pos = fname.lastIndexOf('.'); if (pos == -1) return null; return fname.substring(pos + 1); } static public String getFileExtension(CommonPath p) { String fname = p.getFileName(); if (fname == null) return null; int pos = fname.lastIndexOf('.'); if (pos == -1) return null; return fname.substring(pos + 1); } static public String getFileExtension(String fname) { if (fname == null) return null; int pos = fname.lastIndexOf('.'); if (pos == -1) return null; return fname.substring(pos + 1); } // path to folder creates a temp file in folder // writes 64KB blocks static public Path generateTestFile(Path path, String ext, int minblocks, int maxblocks) { if (!Files.isDirectory(path)) return null; try { if (!Files.exists(path)) Files.createDirectories(path); String fname = FileUtil.randomFilename(ext); path = path.resolve(fname); int blocks = minblocks + FileUtil.testrnd.nextInt(maxblocks - minblocks); // 64KB block byte[] buffer = new byte[64 * 1024]; for (int i = 0; i < buffer.length / 256; i++) for (int j = 0; j < 256; j++) buffer[(i * 256) + j] = (byte)j; ByteBuffer bb = ByteBuffer.wrap(buffer); try (FileChannel fc = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.SYNC)) { while (blocks > 0) { fc.write(bb); blocks--; bb.position(0); // so we can write again } } return path; } catch (IOException x) { System.out.println("generateTestFile Error: " + x); } return null; } static public OperationResult confirmOrCreateDir(Path path) { OperationResult or = new OperationResult(); if (path == null) { or.error("Path is null"); return or; } if (Files.exists(path)) { if (!Files.isDirectory(path)) or.error(path + " exists and is not a directory. Unable to create directory."); return or; } try { Files.createDirectories(path); } catch (FileAlreadyExistsException x) { // someone else created a file under our noses if (!Files.isDirectory(path)) or.error(path + " exists and is not a directory. Unable to create directory."); } catch (Exception x) { or.error("Unable to create directory " + path + ", error: " + x); } return or; } public static OperationResult deleteDirectoryContent(Path directory, String... except) { OperationResult or = new OperationResult(); try { if (Files.exists(directory) && Files.isDirectory(directory)) { try (Stream<Path> strm = Files.list(directory)) { strm.forEach(file -> { for (String exception : except) { if (!file.getFileName().toString().equals(exception)) { if (Files.isDirectory(file)) deleteDirectory(or, file); else try { Files.delete(file); } catch (Exception x) { or.error("Unable to delete file: " + x); } } } }); } } } catch (IOException x) { or.error("Unable to list directory contents: " + x); } return or; } // TODO add secure delete option - JNA? // TODO add delete followup feature someday public static OperationResult deleteDirectory(Path directory) { OperationResult or = new OperationResult(); deleteDirectory(or, directory); return or; } public static void deleteDirectory(OperationResult or, Path directory) { if (directory == null) { or.error("Path is null"); return; } if (Files.notExists(directory)) return; if (!Files.isDirectory(directory)) { or.error("Path is not a folder: " + directory); return; } try { Files.walkFileTree(directory, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path sfile, BasicFileAttributes attrs) throws IOException { Files.delete(sfile); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path sfile, IOException x1) throws IOException { if (x1 != null) throw x1; Files.delete(sfile); return FileVisitResult.CONTINUE; } }); } catch (IOException x) { or.error("Unable to delete directory: " + directory + ", error: " + x); } } /** * Returns an Iterator for the lines in a <code>File</code>. * <p> * This method opens an <code>InputStream</code> for the file. * When you have finished with the iterator you should close the stream * to free internal resources. This can be done by calling the * {@link LineIterator#close()} or * {@link LineIterator#closeQuietly(LineIterator)} method. * <p> * The recommended usage pattern is: * <pre> * LineIterator it = FileUtils.lineIterator(file, "UTF-8"); * try { * while (it.hasNext()) { * String line = it.nextLine(); * /// do something with line * } * } finally { * LineIterator.closeQuietly(iterator); * } * </pre> * <p> * If an exception occurs during the creation of the iterator, the * underlying stream is closed. * * @param file the file to open for input, must not be {@code null} * @param encoding the encoding to use, {@code null} means platform default * @return an Iterator of the lines in the file, never {@code null} * @throws IOException in case of an I/O error (file closed) * @since 1.2 */ public static LineIterator lineIterator(File file, String encoding) throws IOException { try (InputStream in = Files.newInputStream(file.toPath())) { return IOUtil.lineIterator(in, Charset.forName(encoding)); } catch (Exception ex) { throw ex; } } /** * Returns an Iterator for the lines in a <code>File</code> using the default encoding for the VM. * * @param file the file to open for input, must not be {@code null} * @return an Iterator of the lines in the file, never {@code null} * @throws IOException in case of an I/O error (file closed) * @since 1.3 * @see #lineIterator(File, String) */ public static LineIterator lineIterator(final File file) throws IOException { return lineIterator(file, "UTF-8"); } public static OperationResult copyFileTree(Path source, Path target) { return copyFileTree(source, target, null); } public static OperationResult copyFileTree(Path source, Path target, Predicate<Path> filter) { OperationResult or = new OperationResult(); try { if (Files.notExists(target)) Files.createDirectories(target); Files.walkFileTree(source, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { Path targetdir = target.resolve(source.relativize(dir)); try { Files.copy(dir, targetdir, StandardCopyOption.COPY_ATTRIBUTES); } catch (FileAlreadyExistsException x) { if (!Files.isDirectory(targetdir)) throw x; } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if ((filter == null) || filter.test(file)) Files.copy(file, target.resolve(source.relativize(file)), StandardCopyOption.COPY_ATTRIBUTES); return FileVisitResult.CONTINUE; } }); } catch (IOException x) { or.error("Error copying file tree: " + x); } return or; } static public long parseFileSize(String size) { Long x = StringUtil.parseLeadingInt(size); if (x == null) return 0; size = size.toLowerCase(); if (size.endsWith("kb")) x *= 1024; else if (size.endsWith("mb")) x *= 1024 * 1024; else if (size.endsWith("gb")) x *= 1024 * 1024 * 1024; return x; } }