/** * OLAT - Online Learning and Training<br> * http://www.olat.org * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> * University of Zurich, Switzerland. * <hr> * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * This file has been modified by the OpenOLAT community. Changes are licensed * under the Apache 2.0 license as the original file. * <p> */ package org.olat.core.util; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.text.Normalizer; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.IOUtils; import org.olat.core.logging.AssertException; import org.olat.core.logging.OLATRuntimeException; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.springframework.core.io.Resource; /** * @author Mike Stock Comment: */ public class FileUtils { private static final OLog log = Tracing.createLoggerFor(FileUtils.class); private static int buffSize = 32 * 1024; // the following is for cleaning up file I/O stuff ... so it works fine on NFS public static final int BSIZE = 8*1024; // matches files and folders of type: // bla, bla1, bla12, bla.html, bla1.html, bla12.html private static final Pattern fileNamePattern = Pattern.compile("(.+?)\\p{Digit}*(\\.\\w{2,4})?"); //windows: invalid characters for filenames: \ / : * ? " < > | //linux: invalid characters for file/folder names: /, but you have to escape certain chars, like ";$%&*" //OLAT reserved char: ":" public static char[] FILE_NAME_FORBIDDEN_CHARS = { '/', '\n', '\r', '\t', '\f', '`', '?', '*', '\\', '<', '>', '|', '\"', ':', ',' }; //private static char[] FILE_NAME_ACCEPTED_CHARS = { 'ä', 'Ä', 'ü', 'Ü', 'ö', 'Ö', ' '}; public static char[] FILE_NAME_ACCEPTED_CHARS = { '\u0228', '\u0196', '\u0252', '\u0220', '\u0246', '\u0214', ' '}; // known metadata files public static final List<String> META_FILENAMES = Arrays.asList(".DS_Store",".CVS",".nfs",".sass-cache",".hg"); /** * @param sourceFile * @param targetDir * @param filter file filter or NULL if no filter applied * @return true upon success */ public static boolean copyFileToDir(String sourceFile, String targetDir, FileFilter filter, String wt) { return copyFileToDir(new File(sourceFile), new File(targetDir), false, filter, wt); } /** * @param sourceFile * @param targetDir * @return true upon success */ public static boolean copyFileToDir(String sourceFile, String targetDir) { return copyFileToDir(new File(sourceFile), new File(targetDir), false, null); } /** * @param sourceFile * @param targetDir * @param filter file filter or NULL if no filter applied * @return true upon success */ public static boolean moveFileToDir(String sourceFile, String targetDir, FileFilter filter, String wt) { return copyFileToDir(new File(sourceFile), new File(targetDir), true, filter, wt); } /** * @param sourceFile * @param targetDir * @return true upon success */ public static boolean moveFileToDir(String sourceFile, String targetDir) { return copyFileToDir(new File(sourceFile), new File(targetDir), true, null); } /** * @param sourceFile * @param targetDir * @param filter file filter or NULL if no filter applied * @return true upon success */ public static boolean copyFileToDir(File sourceFile, File targetDir, FileFilter filter, String wt) { return copyFileToDir(sourceFile, targetDir, false, filter, wt); } /** * @param sourceFile * @param targetDir * @return true upon success */ public static boolean copyFileToDir(File sourceFile, File targetDir, String wt) { return copyFileToDir(sourceFile, targetDir, false, null, wt); } /** * @param sourceFile * @param targetDir * @param filter file filter or NULL if no filter applied * @return true upon success */ public static boolean moveFileToDir(File sourceFile, File targetDir, FileFilter filter, String wt) { return copyFileToDir(sourceFile, targetDir, true, filter, wt); } /** * @param sourceFile * @param targetDir * @return true upon success */ public static boolean moveFileToDir(File sourceFile, File targetDir) { return copyFileToDir(sourceFile, targetDir, true, null); } /** * @param sourceDir * @param targetDir * @param filter file filter or NULL if no filter applied * @return true upon success */ public static boolean copyDirToDir(String sourceDir, String targetDir, FileFilter filter, String wt) { return copyDirToDir(new File(sourceDir), new File(targetDir), false, filter, wt); } /** * @param sourceDir * @param targetDir * @return true upon success */ public static boolean copyDirToDir(String sourceDir, String targetDir) { return copyDirToDir(new File(sourceDir), new File(targetDir), false, null); } /** * @param sourceDir * @param targetDir * @param filter file filter or NULL if no filter applied * @return true upon success */ public static boolean moveDirToDir(String sourceDir, String targetDir, FileFilter filter, String wt) { return moveDirToDir(new File(sourceDir), new File(targetDir), filter, wt); } /** * @param sourceDir * @param targetDir * @return true upon success */ public static boolean moveDirToDir(String sourceDir, String targetDir, String wt) { return moveDirToDir(new File(sourceDir), new File(targetDir), wt); } /** * @param sourceDir * @param targetDir * @param filter file filter or NULL if no filter applied * @return true upon success */ public static boolean copyDirToDir(File sourceDir, File targetDir, FileFilter filter, String wt) { return copyDirToDir(sourceDir, targetDir, false, filter, wt); } /** * @param sourceDir * @param targetDir * @return true upon success */ public static boolean copyDirToDir(File sourceDir, File targetDir, String wt) { return copyDirToDir(sourceDir, targetDir, false, null, wt); } /** * @param sourceDir * @param targetDir * @param filter file filter or NULL if no filter applied * @return true upon success */ public static boolean moveDirToDir(File sourceDir, File targetDir, FileFilter filter, String wt) { return copyDirInternal(sourceDir, targetDir, true, false, filter, wt); } /** * @param sourceDir * @param targetDir * @return true upon success */ public static boolean moveDirToDir(File sourceDir, File targetDir, String wt) { return copyDirInternal(sourceDir, targetDir, true, false, null, wt); } /** * Get the size in bytes of a directory * * @param path * @return true upon success */ public static long getDirSize(File path) { Iterator<File> path_iterator; File current_file; long size; File[] f = path.listFiles(); if (f == null) { return 0; } path_iterator = (Arrays.asList(f)).iterator(); size = 0; while (path_iterator.hasNext()) { current_file = path_iterator.next(); if (current_file.isFile()) { size += current_file.length(); } else { size += getDirSize(current_file); } } return size; } /** * Copy the contents of a directory from one spot on hard disk to another. Will create any * target dirs if necessary. * * @param sourceDir directory which contents to copy on local hard disk. * @param targetDir new directory to be created on local hard disk. * @param filter file filter or NULL if no filter applied * @param move * @return true if the copy was successful. */ public static boolean copyDirContentsToDir(File sourceDir, File targetDir, boolean move, FileFilter filter, String wt) { return copyDirInternal(sourceDir, targetDir, move, false, filter, wt); } /** * Copy the contents of a directory from one spot on hard disk to another. Will create any * target dirs if necessary. * * @param sourceDir directory which contents to copy on local hard disk. * @param targetDir new directory to be created on local hard disk. * @param move * @return true if the copy was successful. */ public static boolean copyDirContentsToDir(File sourceDir, File targetDir, boolean move, String wt) { return copyDirInternal(sourceDir, targetDir, move, false, null, wt); } /** * Copy a directory from one spot on hard disk to another. Will create any * target dirs if necessary. The directory itself will be created on the target location. * * @param sourceDir directory to copy on local hard disk. * @param targetDir new directory to be created on local hard disk. * @param filter file filter or NULL if no filter applied * @param move * @return true if the copy was successful. */ public static boolean copyDirToDir(File sourceDir, File targetDir, boolean move, FileFilter filter, String wt) { return copyDirInternal(sourceDir, targetDir, move, true, filter, wt); } /** * Copy a directory from one spot on hard disk to another. Will create any * target dirs if necessary. The directory itself will be created on the target location. * * @param sourceDir directory to copy on local hard disk. * @param targetDir new directory to be created on local hard disk. * @param move * @return true if the copy was successful. */ public static boolean copyDirToDir(File sourceDir, File targetDir, boolean move, String wt) { return copyDirInternal(sourceDir, targetDir, move, true, null, wt); } /** * Copy a directory from one spot on hard disk to another. Will create any * target dirs if necessary. * * @param sourceDir directory to copy on local hard disk. * @param targetDir new directory to be created on local hard disk. * @param move * @param createDir If true, a directory with the name of the source directory will be created * @param filter file filter or NULL if no filter applied * @return true if the copy was successful. */ private static boolean copyDirInternal(File sourceDir, File targetDir, boolean move, boolean createDir, FileFilter filter, String wt) { if (sourceDir.isFile()) return copyFileToDir(sourceDir, targetDir, move, filter, wt); if (!sourceDir.isDirectory()) return false; // copy only if filter allows. filtered items are considered a success // and not a failure of the operation if (filter != null && ! filter.accept(sourceDir)) return true; targetDir.mkdirs(); // this will also copy/move empty directories if (!targetDir.isDirectory()) return false; if (createDir) targetDir = new File(targetDir, sourceDir.getName()); if (move) { // in case of move just rename the directory to new location. The operation might fail // on a NFS or when copying accross different filesystems. In such cases, continue and copy // the files instead if (sourceDir.renameTo(targetDir)) return true; } // else copy structure targetDir.mkdirs(); boolean success = true; String[] fileList = sourceDir.list(); if (fileList == null) return false; // I/O error or not a directory for (int i = 0; i < fileList.length; i++) { File f = new File(sourceDir, fileList[i]); if (f.isDirectory()) { success &= copyDirToDir(f, targetDir, move, filter, wt+File.separator+f.getName()); } else { success &= copyFileToDir(f, targetDir, move, filter, wt+" file="+f.getName()); } } // in case of a move accross different filesystems, clean up now if (move) { sourceDir.delete(); } return success; } /** * Copy a file from one spot on hard disk to another. Will create any target * dirs if necessary. * * @param sourceFile file to copy on local hard disk. * @param targetDir new file to be created on local hard disk. * @param move * @param filter file filter or NULL if no filter applied * @return true if the copy was successful. */ public static boolean copyFileToDir(File sourceFile, File targetDir, boolean move, FileFilter filter, String wt) { try { // copy only if filter allows. filtered items are considered a success // and not a failure of the operation if (filter != null && ! filter.accept(sourceFile)) return true; // catch if source is directory by accident if (sourceFile.isDirectory()) { return copyDirToDir(sourceFile, targetDir, move, filter, wt); } // create target directories targetDir.mkdirs(); // don't check for success... would return false on // existing dirs if (!targetDir.isDirectory()) return false; File targetFile = new File(targetDir, sourceFile.getName()); // catch move/copy of "same" file -> buggy under Windows. if (sourceFile.getCanonicalPath().equals(targetFile.getCanonicalPath())) return true; if (move) { // try to rename it first - operation might only be successful on a local filesystem! if (sourceFile.renameTo(targetFile)) return true; // it failed, so continue with copy code! } bcopy (sourceFile, targetFile, "copyFileToDir:"+wt); if (move) { // to finish the move accross different filesystems we need to delete the source file sourceFile.delete(); } } catch (IOException e) { log.error("Could not copy file::" + sourceFile.getAbsolutePath() + " to dir::" + targetDir.getAbsolutePath(), e); return false; } return true; } // end copy public static boolean copyToFile(InputStream in, File targetFile, String wt) { if (targetFile.isDirectory()) return false; // create target directories targetFile.getParentFile().mkdirs(); // don't check for success... would return false on BufferedInputStream bis = new BufferedInputStream(in); try (OutputStream dst = new FileOutputStream(targetFile); BufferedOutputStream bos = getBos (dst)) { cpio (bis, bos, wt); bos.flush(); return true; } catch (IOException e) { throw new RuntimeException("I/O error in cpio "+wt); } finally { IOUtils.closeQuietly(bis); IOUtils.closeQuietly(in); } } /** * Copy method to copy a file to another file * @param sourceFile * @param targetFile * @param move true: move file; false: copy file * @return true: success; false: failure */ public static boolean copyFileToFile(File sourceFile, File targetFile, boolean move) { try { if (sourceFile.isDirectory() || targetFile.isDirectory()) { return false; } // create target directories targetFile.getParentFile().mkdirs(); // don't check for success... would return false on // catch move/copy of "same" file -> buggy under Windows. if (sourceFile.getCanonicalPath().equals(targetFile.getCanonicalPath())) return true; if (move) { // try to rename it first - operation might only be successful on a local filesystem! if (sourceFile.renameTo(targetFile)) return true; // it failed, so continue with copy code! } bcopy (sourceFile, targetFile, "copyFileToFile"); if (move) { // to finish the move accross different filesystems we need to delete the source file sourceFile.delete(); } } catch (IOException e) { log.error("Could not copy file::" + sourceFile.getAbsolutePath() + " to file::" + targetFile.getAbsolutePath(), e); return false; } return true; } // end copy /** * Copy a file from one spot on hard disk to another. Will create any target * dirs if necessary. * * @param sourceFile file to copy on local hard disk. * @param targetDir new file to be created on local hard disk. * @param move * @return true if the copy was successful. */ public static boolean copyFileToDir(File sourceFile, File targetDir, boolean move, String wt) { return copyFileToDir(sourceFile, targetDir, move, null, wt); } /** * Copy an InputStream to an OutputStream. * * @param source InputStream, left open. * @param target OutputStream, left open. * @param length how many bytes to copy. * @return true if the copy was successful. */ public static boolean copy(InputStream source, OutputStream target, long length) { if (length == 0) return true; try { int chunkSize = (int) Math.min(buffSize, length); long chunks = length / chunkSize; int lastChunkSize = (int) (length % chunkSize); // code will work even when chunkSize = 0 or chunks = 0; byte[] ba = new byte[chunkSize]; for (long i = 0; i < chunks; i++) { int bytesRead = readBlocking(source, ba, 0, chunkSize); if (bytesRead != chunkSize) { throw new IOException(); } target.write(ba); } // end for // R E A D / W R I T E last chunk, if any if (lastChunkSize > 0) { int bytesRead = readBlocking(source, ba, 0, lastChunkSize); if (bytesRead != lastChunkSize) { throw new IOException(); } target.write(ba, 0, lastChunkSize); } // end if } catch (IOException e) { // don't log as error - happens all the time (ClientAbortException) if (log.isDebug()) log.debug("Could not copy stream::" + e.getMessage() + " with length::" + length); return false; } return true; } // end copy /** * Copy an InputStream to an OutputStream, until EOF. Use only when you don't * know the length. * * @param source InputStream, left open. * @param target OutputStream, left open. * @return true if the copy was successful. */ public static boolean copy(InputStream source, OutputStream target) { try { int chunkSize = buffSize; // code will work even when chunkSize = 0 or chunks = 0; // Even for small files, we allocate a big buffer, since we // don't know the size ahead of time. byte[] ba = new byte[chunkSize]; while (true) { int bytesRead = readBlocking(source, ba, 0, chunkSize); if (bytesRead > 0) { target.write(ba, 0, bytesRead); } else { break; } // hit eof } // end while } catch (IOException e) { // don't log as error - happens all the time (ClientAbortException) if (log.isDebug()) log.debug("Could not copy stream::" + e.getMessage()); return false; } return true; } /** * Reads exactly <code>len</code> bytes from the input stream into the byte * array. This method reads repeatedly from the underlying stream until all * the bytes are read. InputStream.read is often documented to block like * this, but in actuality it does not always do so, and returns early with * just a few bytes. readBlockiyng blocks until all the bytes are read, the * end of the stream is detected, or an exception is thrown. You will always * get as many bytes as you asked for unless you get an eof or other * exception. Unlike readFully, you find out how many bytes you did get. * * @param in * @param b the buffer into which the data is read. * @param off the start offset of the data. * @param len the number of bytes to read. * @return number of bytes actually read. * @exception IOException if an I/O error occurs. */ public static final int readBlocking(InputStream in, byte b[], int off, int len) throws IOException { int totalBytesRead = 0; while (totalBytesRead < len) { int bytesRead = in.read(b, off + totalBytesRead, len - totalBytesRead); if (bytesRead < 0) { break; } totalBytesRead += bytesRead; } return totalBytesRead; } // end readBlocking public static void deleteDirsAndFiles(Path path) throws IOException { Files.walkFileTree(path, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { Files.delete(dir); return FileVisitResult.CONTINUE; } }); } /** * Get rid of ALL files and subdirectories in given directory, and all subdirs * under it, * * @param dir would normally be an existing directory, can be a file aswell * @param recursive true if you want subdirs deleted as well * @param deleteDir true if dir needs to be deleted as well * @return true upon success */ public static boolean deleteDirsAndFiles(File dir, boolean recursive, boolean deleteDir) { boolean success = true; if (dir == null) return false; // We must empty child subdirs contents before can get rid of immediate // child subdirs if (recursive) { String[] allDirs = dir.list(); if (allDirs != null) { for (int i = 0; i < allDirs.length; i++) { success &= deleteDirsAndFiles(new File(dir, allDirs[i]), true, false); } } } // delete all files in this dir String[] allFiles = dir.list(); if (allFiles != null) { for (int i = 0; i < allFiles.length; i++) { File deleteFile = new File(dir, allFiles[i]); success &= deleteFile.delete(); } } // delete passed dir if (deleteDir) { success &= dir.delete(); } return success; } // end deleteDirContents /** * @param newF */ public static void createEmptyFile(File newF) { try { FileOutputStream fos = new FileOutputStream(newF); fos.close(); } catch (IOException e) { throw new AssertException("empty file could not be created for path " + newF.getAbsolutePath(), e); } } /** * @param baseDir * @param fileVisitor */ public static void visitRecursively(File baseDir, FileVisitor fileVisitor) { visit(baseDir, fileVisitor); } private static void visit(File file, FileVisitor fileVisitor) { if (file.isDirectory()) { File[] files = file.listFiles(); for (int i = 0; i < files.length; i++) { File f = files[i]; visit(f, fileVisitor); } } else { // regular file fileVisitor.visit(file); } } /** * @param target * @param data * @param encoding */ public static void save(File target, String data, String encoding) { try { save(new FileOutputStream(target), data, StringHelper.check4xMacRoman(encoding)); } catch (IOException e) { throw new OLATRuntimeException(FileUtils.class, "could not save file", e); } } public static InputStream getInputStream(String data, String encoding) { try { byte[] ba = data.getBytes(StringHelper.check4xMacRoman(encoding)); ByteArrayInputStream bis = new ByteArrayInputStream(ba); return bis; } catch (IOException e) { throw new OLATRuntimeException(FileUtils.class, "could not save to output stream", e); } } /** * @param target * @param data * @param encoding */ public static void save(OutputStream target, String data, String encoding) { try { byte[] ba = data.getBytes(StringHelper.check4xMacRoman(encoding)); ByteArrayInputStream bis = new ByteArrayInputStream(ba); bcopy (bis, target, "saveDataToFile"); } catch (IOException e) { throw new OLATRuntimeException(FileUtils.class, "could not save to output stream", e); } } /** * Save a given input stream to a file * @param source the input stream * @param target the file */ public static void save(InputStream source, File target) { try { BufferedInputStream bis = new BufferedInputStream(source); BufferedOutputStream bos = getBos(target); cpio (bis, bos, "fileSave"); bos.flush(); bos.close(); bis.close(); } catch (IOException e) { throw new OLATRuntimeException(FileUtils.class, "could not save stream to file::" + target.getAbsolutePath(), e); } } public static String load(Resource source, String encoding) { try { return load(source.getInputStream(), encoding); } catch (FileNotFoundException e) { throw new RuntimeException("File not found: " + source.getFilename()); } catch (IOException e) { throw new RuntimeException("File not found: " + source.getFilename()); } } /** * @param source * @param encoding * @return the file in form of a string */ public static String load(File source, String encoding) { try { return load(new FileInputStream(source), encoding); } catch (FileNotFoundException e) { throw new RuntimeException("could not copy file to ram: " + source.getAbsolutePath()); } } /** * @param source * @param encoding * @return the inpustream in form of a string */ public static String load(InputStream source, String encoding) { String htmltext = null; try { ByteArrayOutputStream bas = new ByteArrayOutputStream(); boolean success = FileUtils.copy(source, bas); source.close(); bas.close(); if (!success) throw new RuntimeException("could not copy inputstream to ram"); htmltext = bas.toString(StringHelper.check4xMacRoman(encoding)); } catch (IOException e) { throw new OLATRuntimeException(FileUtils.class, "could not load from inputstream", e); } return htmltext; } /** * checks whether the given File is a Directory and if it contains any files or sub-directories * * * @return returns true if given File-object is a directory and contains any files or subdirectories */ public static boolean isDirectoryAndNotEmpty(File directory){ String[] content = directory.list(); if(content == null) return false; return (content.length > 0); } /** * @param is the inputstream to close, may also be null */ public static void closeSafely(InputStream is) { if (is == null) return; try { is.close(); } catch (IOException e) { // nothing to do } } /** * @param os the outputstream to close, may also be null */ public static void closeSafely(OutputStream os) { if (os == null) return; try { os.close(); } catch (IOException e) { // nothing to do } } /** * Extract file suffix. E.g. 'html' from index.html * @param filePath * @return return empty String "" without suffix. */ public static String getFileSuffix(String filePath) { if(StringHelper.containsNonWhitespace(filePath)) { int lastDot = filePath.lastIndexOf('.'); if (lastDot > 0) { if (lastDot < filePath.length()) return filePath.substring(lastDot + 1).toLowerCase(); } } return ""; } /** * Simple check for filename validity. * It compares each character if it is accepted, forbidden or in a certain (Latin-1) range. <p> * Characters < 33 --> control characters and space * Characters > 255 --> above ASCII * http://www.danshort.com/ASCIImap/ * TODO: control chars from 127 - 157 should also not be accepted * TODO: how about non ascii chars in filenames, they should also work! See: OLAT-5704 * * @param filename * @return true if filename valid */ public static boolean validateFilename(String filename) { if(filename==null) { return false; } Arrays.sort(FILE_NAME_FORBIDDEN_CHARS); Arrays.sort(FILE_NAME_ACCEPTED_CHARS); for(int i=0; i<filename.length(); i++) { char character = filename.charAt(i); if(Arrays.binarySearch(FILE_NAME_ACCEPTED_CHARS, character)>=0) { continue; } else if(character<33 || character>255 || Arrays.binarySearch(FILE_NAME_FORBIDDEN_CHARS, character)>=0) { return false; } } //check if there are any unwanted path denominators in the name if (filename.indexOf("..") > -1) { return false; } return true; } public static String normalizeFilename(String name) { String nameFirstPass = name.replace(" ", "_") .replace("\u00C4", "Ae") .replace("\u00D6", "Oe") .replace("\u00DC", "Ue") .replace("\u00E4", "ae") .replace("\u00F6", "oe") .replace("\u00FC", "ue") .replace("\u00DF", "ss") .replace("\u00F8", "o") .replace("\u2205", "o") .replace("\u00E6", "ae"); String nameNormalized = Normalizer.normalize(nameFirstPass, Normalizer.Form.NFKD) .replaceAll("\\p{InCombiningDiacriticalMarks}+",""); String nameSanitized = nameNormalized.replaceAll("\\W+", ""); return nameSanitized; } /** * Creates a new directory in the specified directory, using the given prefix and suffix strings to generate its name. * It uses File.createTempFile() and should provide a unique name. * @param prefix * @param suffix * @param directory * @return */ public static File createTempDir(String prefix, String suffix, File directory) { File tmpDir = null; try { File tmpFile = File.createTempFile(prefix, suffix, directory); if(tmpFile.exists()) { tmpFile.delete(); } boolean tmpDirCreated = tmpFile.mkdir(); if(tmpDirCreated) { tmpDir = tmpFile; } } catch (Exception e) { //bummer! } return tmpDir; } public static void bcopy (File src, File dst, String wt) throws FileNotFoundException, IOException { bcopy (new FileInputStream(src), new FileOutputStream(dst), wt); } public static void bcopy (InputStream src, File dst, String wt) throws FileNotFoundException, IOException { bcopy (src, new FileOutputStream(dst), "copyStreamToFile:"+wt); } public static BufferedOutputStream getBos (FileOutputStream of) { return new BufferedOutputStream (of, BSIZE); } public static BufferedOutputStream getBos (OutputStream os) { return new BufferedOutputStream (os, BSIZE); } public static BufferedOutputStream getBos (File of) throws FileNotFoundException { return getBos (new FileOutputStream(of)); } public static BufferedOutputStream getBos (String fname) throws FileNotFoundException { return getBos (new File (fname)); } /** * Buffered copy streams (closes both streams when done) * * @param src InputStream * @param dst OutputStream * @throws IOException */ public static void bcopy (InputStream src, OutputStream dst, String wt) throws IOException { BufferedInputStream bis = new BufferedInputStream(src); BufferedOutputStream bos = getBos (dst); try { cpio (bis, bos, wt); bos.flush(); } catch (IOException e) { throw new RuntimeException("I/O error in cpio "+wt); } finally { bos.close(); dst.close(); bis.close(); // no effect src.close(); // no effect } } /** * copy in, copy out (leaves both streams open) * <p> * @see FileUtils.getBos() which creates a matching BufferedOutputStream * </p> * * @param in BuferedInputStream * @param out BufferedOutputStream * @param wt What this I/O is about */ public static long cpio (InputStream in, OutputStream out, String wt) throws IOException { byte[] buffer = new byte[BSIZE]; int c; long tot = 0; long s = 0; boolean debug = log.isDebug(); if(debug) { s = System.nanoTime(); } while ((c = in.read(buffer, 0, buffer.length)) != -1) { out.write(buffer, 0, c); tot += c; } if(debug) { long tim = System.nanoTime() - s; double dtim = tim == 0 ? 0.5 : tim; // avg of those less than 1 nanoseconds is taken as 0.5 nanoseconds double bps = tot*1000*1000/dtim; log.debug(String.format("cpio %,13d bytes %6.2f ms avg %6.1f Mbps %s%n", tot, dtim/1000/1000, bps/1024, wt)); } return tot; } /** * from a newer version of apache commons.io Determines whether the specified * file is a Symbolic Link rather than an actual file. * <p> * Will not return true if there is a Symbolic Link anywhere in the path, only * if the specific file is. * * @param file the file to check * @return true if the file is a Symbolic Link * @throws IOException if an IO error occurs while checking the file * @since Commons IO 2.0 */ public static boolean isSymlink(File file) throws IOException { if (file == null) { throw new NullPointerException("File must not be null"); } if ("\\".equals(File.separatorChar) ) { return false; } // Windows doesn't know symlinks! File fileInCanonicalDir = null; if (file.getParent() == null) { fileInCanonicalDir = file; } else { File canonicalDir = file.getParentFile().getCanonicalFile(); fileInCanonicalDir = new File(canonicalDir, file.getName()); } if (fileInCanonicalDir.getCanonicalFile().equals(fileInCanonicalDir.getAbsoluteFile())) { return false; } else { return true; } } /** * Check if the given filename is a metadata filename generated by macOS or * windows when browsing a directory or generated by one of the known * repository systems. * * @param filename * @return */ public static boolean isMetaFilename(String filename) { boolean isMeta = false; if (filename != null) { // 1) check for various known filenames isMeta = META_FILENAMES.parallelStream().anyMatch(filename::contains); if (!isMeta) { // 2) macOS meta files generated with WebDAV starts with ._ isMeta = filename.startsWith("._"); } } return isMeta; } public static String rename(File f) { String filename = f.getName(); String newName = filename; File newFile = f; for(int count=0; newFile.exists() && count < 999 ; ) { count++; newName = appendNumberAtTheEndOfFilename(filename, count); newFile = new File(f.getParentFile(), newName); } if(!newFile.exists()) { return newName; } return null; } /** * Sticks together a new filename. If there's a match with a common filename * with extension, add the counter to the end of the filename before the * extension. Else just add the counter to the end of the name. E.g.: * hello.xml => hello1.xml where 1 is the counter * hello1.xml => hello2.xml * blaber => blaber1 * blaber1 => blaber2 * * @param name * @param number * @return The new name with the counter added */ public static String appendNumberAtTheEndOfFilename(String name, int number) { // Try to match the file to the pattern "[name].[extension]" Matcher m = fileNamePattern.matcher(name); StringBuilder newName = new StringBuilder(); if (m.matches()) { newName.append(m.group(1)).append(number); if (m.group(2) != null) { // is null in case it was not a file or does not contain a file ending. newName.append(m.group(2)); } } else { newName.append(name).append(number); } return newName.toString(); } }