All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.peterphi.std.io.FileHelper Maven / Gradle / Ivy

The newest version!
package com.peterphi.std.io;

import com.peterphi.std.system.exec.Exec;
import com.peterphi.std.system.exec.Execed;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.Files;
import java.util.Set;

/**
 * 

* Title: File Helper *

*

* Description: File-related helper functions *

*

* Copyright: Copyright (c) 2006-2009 *

*

*

*

* * @version $Revision$ */ public class FileHelper { private static final transient Logger log = Logger.getLogger(FileHelper.class); private FileHelper() { } // Prevent instantiation /** * Creates a temporary file name * * @return File */ public static File createTempFile(final String prefix, final String suffix) { try { File tempFile = File.createTempFile(prefix, suffix); if (tempFile.exists()) { if (!tempFile.delete()) throw new RuntimeException("Could not delete new temp file: " + tempFile); } return tempFile; } catch (IOException e) { log.error("[FileHelper] {createTempFile} Error creating temp file: " + e.getMessage(), e); return null; } } /** * Ensures that a file is "safe" (this is done by whitelisting a small number of characters)
* This method is mainly designed for producing a filename which bears some resemblance to a user-supplied String while * removing the risk of being tricked * * @param filename * * @return */ public static String enforceSafeFilename(final String filename) { final char safeChar = '.'; char[] chars = filename.toCharArray(); for (int i = 0; i < chars.length; i++) { if (!isSafeFilenameCharacter(chars[i])) chars[i] = safeChar; } return String.valueOf(chars); } /** * Determines if a character is a valid "safe filename" character (which we restrict to: alpha, digits, "-", "_" and ".") * * @param c * * @return */ private static boolean isSafeFilenameCharacter(char c) { if (Character.isLetterOrDigit(c)) { return true; } else if (c == '-' || c == '_' || c == '.') { return true; } else { return false; } } public static boolean isAbsoluteFile(File f) { try { return f.isAbsolute(); } catch (Throwable t) { return false; } } public static boolean isAbsoluteFile(String filename) { return isAbsoluteFile(new File(filename)); } public static boolean isAncestor(File root, File f) { while (f != null && !f.equals(root)) f = f.getParentFile(); return f != null; } public static boolean couldWrite(final File f) { if (f == null) throw new IllegalArgumentException("Must specify a non-null file!"); // If the file exists check if we can write to it // If the file doesn't exist, try to create it. If we can't then the caller won't be able to write to it if they try // If the file doesn't exist and the parent directory doesn't exist if (!f.exists()) { try { if (f.getParentFile().exists()) { try { FileHelper.write(f, ""); // should always exist - test in case there was a silently dropped error return f.exists(); } finally { if (!f.delete()) log.warn("Could not delete file once written: " + f); } } else { log.info("{couldWrite} Could not determine if the file is writable because the parent directory did not exist"); return false; } } catch (IOException e) { log.warn("{couldWrite} Error while testing: " + e.getMessage(), e); return false; } } else { return f.canWrite(); } } public static void copy(File src, File dest) throws IOException { if (!src.exists()) throw new FileNotFoundException("Cannot copy from non-existant source " + src.getAbsolutePath() + " to " + dest.getAbsolutePath()); if (log.isInfoEnabled()) log.info("[FileHelper] {copy} Copying " + src + " to " + dest); if (src.isDirectory()) { FileUtils.copyDirectory(src, dest); } else { if (dest.exists()) { delete(dest); } FileUtils.copyFile(src, dest); if (log.isTraceEnabled()) { if (src.length() != dest.length()) { log.trace("[FileHelper] {copy} src and dest have different sizes at the end of the copy process!"); } else { log.trace("[FileHelper] {copy} Success: src and dest are identically sized"); } } } } public static void move(File src, File dest) throws IOException { if (!src.exists()) throw new FileNotFoundException("Cannot copy from non-existant source " + src.getAbsolutePath() + " to " + dest.getAbsolutePath()); if (log.isInfoEnabled()) log.info("[FileHelper] {move} Moving " + src + " to " + dest); if (!src.renameTo(dest)) { // Try a fast move (works if the files are on the same partition) if (src.isDirectory()) { FileUtils.copyDirectory(src, dest); FileUtils.deleteDirectory(src); } else { FileUtils.copyFile(src, dest); src.delete(); } } } public static boolean trySymlink(final File from, final File to) { try { symlink(from, to); return true; } catch (IOException e) { log.error("[FileHelper] {trySymlink} Failure: " + e.getMessage(), e); return false; } } public static void symlink(final File from, final File to) throws IOException { try { if (!from.exists()) { throw new FileNotFoundException("Symbolic link source does not exist: " + from); } // Delete the destination if necessary if (to.exists()) if (!to.delete()) throw new IOException("Could not delete file: " + to); // Launch the link process final int returnCode = Exec.utilityAs(null, "ln", "-s", from.toString(), to.toString()).waitForExit(); if (returnCode != 0) { throw new IOException("ln command returned nonzero return code: " + returnCode); } else if (!to.exists()) { throw new IOException("Symbolic link does not exist (but ln claimed successful execution)"); } else if (!to.getCanonicalFile().equals(from.getCanonicalFile())) { throw new IOException("Link points to the wrong place (to.getCanonicalFile() != from.getCanonicalFile()"); } } catch (IOException e) { log.error("[FileHelper] {symlink} Failure: " + e.getMessage(), e); throw e; } } /** * Safely moves a file from one place to another, ensuring the filesystem is left in a consistent state * * @param src * File The source file * @param dest * File The destination file * * @return boolean True if the file has been completely moved to the new location, false if it is still in the original * location * * @throws java.lang.SecurityException * MAY BE THROWN if permission is denied to src or dest * @deprecated use commons file utils FileUtils.moveDirectoryToDirectory instead */ @Deprecated public static boolean safeMove(File src, File dest) throws SecurityException { assert (src.exists()); final boolean createDestIfNotExist = true; try { if (src.isFile()) FileUtils.moveFile(src, dest); else FileUtils.moveDirectoryToDirectory(src, dest, createDestIfNotExist); return true; } catch (IOException e) { log.error("{safeMove} Error during move operation: " + e.getMessage(), e); return false; } } /** * Deletes a local file or directory from the filesystem * * @param f * File The file/directory to delete * * @return boolean True if the deletion was a success, otherwise false */ public static boolean delete(File f) throws IOException { assert (f.exists()); if (f.isDirectory()) { FileUtils.deleteDirectory(f); return true; } else { return f.delete(); } } /** * Determines if 2 files or directories are equivalent by looking inside them * * @param one * File The first file/directory * @param two * File The second file/directory * @param checkName * boolean Whether names should be identical also * * @return boolean True if the files/directories are equivalent, otherwise false * * @throws IOException * On an unhandleable error or a non-file, non-directory input */ public static boolean smartEquals(File one, File two, boolean checkName) throws IOException { if (checkName) { if (!one.getName().equals(two.getName())) { return false; } } if (one.isDirectory() == two.isDirectory()) { if (one.isDirectory()) { File[] filesOne = one.listFiles(); File[] filesTwo = two.listFiles(); if (filesOne.length == filesTwo.length) { if (filesOne.length > 0) { for (int i = 0; i < filesOne.length; i++) { if (!smartEquals(filesOne[i], filesTwo[i], checkName)) { return false; } } return true; // all subfiles are equal } else { return true; } } else { return false; } } // Otherwise, the File objects are Files else if (one.isFile() && two.isFile()) { if (one.length() == two.length()) { return FileUtils.contentEquals(one, two); } else { return false; } } else { throw new IOException("I don't know how to handle a non-file non-directory File: one=" + one + " two=" + two); } } // One is a directory and the other is not else { return false; } } /** * Reads a PID (Process Id) file * * @param f * File The process Id file (must exist!) * * @return String The process Id represented by the file (or -1 if the file doesn't exist) * * @throws IOException * On filesystem-level errors */ public static long readPID(File f) throws IOException { return readPID(f, false); } /** * Reads a PID (Process Id) file * * @param f * File The process Id file (must exist!) * @param carefulProcessing * boolean If true, non-numeric chars are stripped from the PID before it is parsed * * @return String The process Id represented by the file (or -1 if the file doesn't exist) * * @throws IOException * On filesystem-level errors */ public static long readPID(File f, boolean carefulProcessing) throws IOException { if (f.exists()) { String pidString = cat(f); if (carefulProcessing) pidString = pidString.replaceAll("[^0-9]", ""); // Strip out anything that's not a number else pidString = pidString.replace("\n", ""); // Just remove newlines if (pidString.length() > 0) return Long.parseLong(pidString); else return -1; } else { return -1; } } public static void writePID(File f, long pid) throws IOException { writePID(f, Long.toString(pid)); } public static void writePID(File f, String pid) throws IOException { assert (pid != null && pid.length() != 0) : "Must supply a valid PID!"; write(f, pid); } public static void write(File f, String contents) throws IOException { write(f, new StringReader(contents)); } public static void write(File f, StringBuilder sb) throws IOException { write(f, new StringBuilderReader(sb)); } public static void write(File f, Reader fr) throws IOException { assert (f != null) : "Must supply a file to write to!"; assert (fr != null) : "Must supply a Reader to read from!"; FileWriter fw = new FileWriter(f); try { char[] buffer = new char[4096]; int size = 0; while ((size = fr.read(buffer)) != -1) { fw.write(buffer, 0, size); } } finally { fw.close(); fr.close(); } } public static String cat(String filename) throws IOException { return cat(new File(filename)); } public static String cat(InputStream is) throws IOException { return cat(new InputStreamReader(is), 1024); } public static String cat(final URL u) throws IOException { return cat(u.openStream()); } public static String cat(final Reader reader) throws IOException { return cat(reader, 1024); } public static String cat(final File f) throws IOException { FileInputStream fis = new FileInputStream(f); try { return cat(fis); } finally { fis.close(); } } public static String cat(final Reader reader, final int sizeEstimate) throws IOException { assert (reader != null) : "Must provide a reader to read from!"; try { StringBuilder sb = new StringBuilder(sizeEstimate); char[] buffer = new char[4096]; int read = 0; while ((read = reader.read(buffer)) != -1) { sb.append(buffer, 0, read); } return sb.toString(); } finally { reader.close(); } } public static boolean chown(final File f, String owner, String group, boolean recursive) throws IOException { if (!f.exists()) throw new FileNotFoundException("Cannot chown a non-existant file!"); if (owner == null) owner = ""; else if (group == null) group = ""; final String ownerGroupPair; if (owner.isEmpty() && group.isEmpty()) throw new IllegalArgumentException("Must specify an owner or a group to change ownership to"); else if (group.isEmpty()) ownerGroupPair = owner; else ownerGroupPair = owner + "." + group; try { final String[] cmd; if (recursive) cmd = new String[]{"chown", "--recursive", ownerGroupPair, f.getPath()}; else cmd = new String[]{"chown", ownerGroupPair, f.getPath()}; Execed call = Exec.rootUtility(cmd); int returnCode = call.waitForExit(); return returnCode == 0; } catch (Exception e) { log.error("[FileHelper] {chown} Failure: " + e.getMessage(), e); return false; } } /** * Performs a chmod (which assumes this system is Linux/UNIX/Solaris/etc), replacing the permissions using octal * * @param f * @param permissions * REMEMBER TO SPECIFY THIS VALUE IN OCTAL (ie. with a leading zero) IF YOU ARE USING NUMBERS IDENTICAL TO THE * CHMOD * COMMAND-LINE REPRESENTATION (eg. 755) * * @return * * @throws IOException */ public static boolean chmod(final File f, final int permissions) { return chmod(null, f, permissions); } /** * Performs a chmod (which assumes this system is Linux/UNIX/Solaris/etc), replacing the permissions using octal * * @param f * @param permissions * REMEMBER TO SPECIFY THIS VALUE IN OCTAL (ie. with a leading zero) * * @return * * @throws IOException */ public static boolean chmod(final String as, final File f, final int permissions) { if (!f.exists()) { log.error("[FileHelper] {chmod} Non-existant file: " + f.getPath()); return false; } try { Execed call = Exec.utilityAs(as, "chmod", Integer.toOctalString(permissions), f.getPath()); int returnCode = call.waitForExit(); return returnCode == 0; } catch (Exception e) { log.error("[FileHelper] {chmod} Failure: " + e.getMessage(), e); return false; } } /** * Performs a chmod (which assumes this system is Linux/UNIX/Solaris/etc), replacing the permissions on f with * the permissions on copyOf * * @param f * @param copyOf * the file to use the permissions from * * @return * * @throws IOException */ public static boolean chmod(File f, File copyOf) { return chmod(null, f, copyOf); } /** * Performs a chmod (which assumes this system is Linux/UNIX/Solaris/etc), replacing the permissions on f with * the permissions on copyOf * * @param f * @param copyOf * the file to use the permissions from * * @return * * @throws IOException */ public static boolean chmod(String as, File f, File copyOf) { if (!f.exists()) { log.error("[FileHelper] {chmod} Non-existant file: " + f.getPath()); return false; } if (!copyOf.exists()) { log.error("[FileHelper] {chmod} Non-existant file: " + copyOf.getPath()); return false; } try { Execed call = Exec.utilityAs(as, "chmod", "--reference=" + copyOf.getPath(), f.getPath()); int returnCode = call.waitForExit(); return returnCode == 0; } catch (Exception e) { log.error("[LockRecord] {chmod} Failure: " + e.getMessage(), e); return false; } } /** * Performs a chmod (which assumes this system is Linux/UNIX/Solaris/etc), altering the permissions using symbols (ie. chmod * o+w) * * @param f * @param set * The permissions to set on the file * * @return * * @throws IOException */ public static boolean chmod(String as, File f, Set set) { return chmod(as, f, set, null); } /** * Performs a chmod (which assumes this system is Linux/UNIX/Solaris/etc), altering the permissions using symbols (ie. chmod * o+w) * * @param f * @param set * The permissions to set on the file * * @return * * @throws IOException */ public static boolean chmod(File f, Set set) { return chmod(null, f, set); } /** * Performs a chmod (which assumes this system is Linux/UNIX/Solaris/etc), altering the permissions using symbols (ie. chmod * o+w) * * @param f * @param set * The permissions to set on the file * @param clear * The permissions to modify on the file * * @return * * @throws IOException */ public static boolean chmod(File f, Set set, Set clear) { return chmod(null, f, set, clear); } /** * Performs a chmod (which assumes this system is Linux/UNIX/Solaris/etc), altering the permissions using symbols (ie. chmod * o+w) * * @param f * @param set * The permissions to set on the file * @param clear * The permissions to modify on the file * * @return * * @throws IOException */ public static boolean chmod(String as, File f, Set set, Set clear) { if (!f.exists()) { log.error("[FileHelper] {chmod} Non-existant file: " + f.getPath()); return false; } String permissions = ChmodBit.toString(set, clear); try { Execed call = Exec.utilityAs(as, "chmod", permissions, f.getPath()); int returnCode = call.waitForExit(); return returnCode == 0; } catch (Exception e) { log.error("[LockRecord] {chmod} Failure: " + e.getMessage(), e); return false; } } public static class LockRecord { protected FileOutputStream fos; protected FileChannel channel; protected FileLock flock; public void release() { try { if (flock != null && flock.isValid()) { flock.release(); } if (channel != null && channel.isOpen()) { channel.close(); } if (fos != null) { fos.close(); } } catch (IOException e) { // ignore } } } /** * Obtains an exclusive lock on the specified file. By calling this method, the caller guarantees it will call the release() * method of the LockRecord * * @param f * File The file to lock * * @return LockRecord The lock record */ public static LockRecord lockFile(File f) { LockRecord rec = new LockRecord(); try { rec.fos = new FileOutputStream(f); rec.channel = rec.fos.getChannel(); rec.flock = rec.channel.lock(); } catch (IOException e) { log.error("[FileHelper] {lockFile} Error while locking " + f + ". Error: " + e.getMessage(), e); if (rec != null) { rec.release(); } return null; } return rec; } /** * Creates/updates a file with the modify date set to now * * @param f */ public static void touch(File f) throws IOException { if (f.createNewFile()) { // The modify time is already set } else { boolean success = f.setLastModified(System.currentTimeMillis()); if (!success) log.warn("[FileHelper] {touch} Failed to update modify time on " + f); } } /** * Create a directory and any necessary parent directories, throwing a {@link RuntimeException} on failure * * @param f * the directory to create * * @throws RuntimeException * wrapping the inner IOException if the filesystem cannot create a directory */ public static void mkdirs(File f) { try { Files.createDirectories(f.toPath()); } catch (IOException e) { throw new RuntimeException("Could not create directory: " + e.getMessage(), e); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy