diff --git a/source/net/sourceforge/filebot/StandardRenameAction.java b/source/net/sourceforge/filebot/StandardRenameAction.java index 7b507516..35e277b7 100644 --- a/source/net/sourceforge/filebot/StandardRenameAction.java +++ b/source/net/sourceforge/filebot/StandardRenameAction.java @@ -1,130 +1,124 @@ - package net.sourceforge.filebot; - import java.io.File; import java.io.IOException; import net.sourceforge.tuned.FileUtilities; - public enum StandardRenameAction implements RenameAction { - + MOVE { - + @Override public File rename(File from, File to) throws Exception { return FileUtilities.moveRename(from, to); } }, - + COPY { - + @Override public File rename(File from, File to) throws Exception { return FileUtilities.copyAs(from, to); } }, - + KEEPLINK { - + @Override public File rename(File from, File to) throws Exception { File destionation = FileUtilities.resolveDestination(from, to, true); - + // move file and the create a symlink to the new location via NIO.2 try { java.nio.file.Files.move(from.toPath(), destionation.toPath()); - java.nio.file.Files.createSymbolicLink(from.toPath(), destionation.toPath()); + FileUtilities.createRelativeSymlink(from, destionation, true); } catch (LinkageError e) { throw new Exception("Unsupported Operation: move, createSymbolicLink"); } - + return destionation; } }, - + SYMLINK { - + @Override public File rename(File from, File to) throws Exception { File destionation = FileUtilities.resolveDestination(from, to, true); - + // create symlink via NIO.2 try { - java.nio.file.Files.createSymbolicLink(destionation.toPath(), from.toPath()); + return FileUtilities.createRelativeSymlink(destionation, from, true); } catch (LinkageError e) { throw new Exception("Unsupported Operation: createSymbolicLink"); } - - return destionation; } }, - + HARDLINK { - + @Override public File rename(File from, File to) throws Exception { File destionation = FileUtilities.resolveDestination(from, to, true); - + // create hardlink via NIO.2 try { java.nio.file.Files.createLink(destionation.toPath(), from.toPath()); } catch (LinkageError e) { throw new Exception("Unsupported Operation: createLink"); } - + return destionation; } }, - + RENAME { - + @Override public File rename(File from, File to) throws IOException { // rename only the filename File dest = new File(from.getParentFile(), to.getName()); - + if (!from.renameTo(dest)) throw new IOException("Rename failed: " + dest); - + return dest; } }, - + TEST { - + @Override public File rename(File from, File to) throws IOException { return FileUtilities.resolveDestination(from, to, false); } }; - + public String getDisplayName() { switch (this) { - case MOVE: - return "Rename"; - case COPY: - return "Copy"; - case KEEPLINK: - return "Keeplink"; - case SYMLINK: - return "Symlink"; - case HARDLINK: - return "Hardlink"; - default: - return null; + case MOVE: + return "Rename"; + case COPY: + return "Copy"; + case KEEPLINK: + return "Keeplink"; + case SYMLINK: + return "Symlink"; + case HARDLINK: + return "Hardlink"; + default: + return null; } } - - + public static StandardRenameAction forName(String action) { for (StandardRenameAction it : values()) { if (it.name().equalsIgnoreCase(action)) return it; } - + throw new IllegalArgumentException("Illegal rename action: " + action); } - + } diff --git a/source/net/sourceforge/tuned/FileUtilities.java b/source/net/sourceforge/tuned/FileUtilities.java index 81fec905..3baed578 100644 --- a/source/net/sourceforge/tuned/FileUtilities.java +++ b/source/net/sourceforge/tuned/FileUtilities.java @@ -1,7 +1,5 @@ - package net.sourceforge.tuned; - import java.io.BufferedInputStream; import java.io.File; import java.io.FileFilter; @@ -40,13 +38,12 @@ import org.w3c.dom.Document; import com.ibm.icu.text.CharsetDetector; import com.ibm.icu.text.CharsetMatch; - public final class FileUtilities { - + public static File moveRename(File source, File destination) throws IOException { // resolve destination destination = resolveDestination(source, destination, true); - + if (source.isDirectory()) { // move folder org.apache.commons.io.FileUtils.moveDirectory(source, destination); @@ -58,15 +55,14 @@ public final class FileUtilities { org.apache.commons.io.FileUtils.moveFile(source, destination); // use "copy and delete" as fallback if standard rename fails } } - + return destination; } - - + public static File copyAs(File source, File destination) throws IOException { // resolve destination destination = resolveDestination(source, destination, true); - + if (source.isDirectory()) { // copy folder org.apache.commons.io.FileUtils.copyDirectory(source, destination); @@ -78,104 +74,110 @@ public final class FileUtilities { org.apache.commons.io.FileUtils.copyFile(source, destination); } } - + return destination; } - - + public static File resolveDestination(File source, File destination, boolean mkdirs) throws IOException { // resolve destination if (!destination.isAbsolute()) { // same folder, different name destination = new File(source.getParentFile(), destination.getPath()); } - + // make sure we that we can create the destination folder structure File destinationFolder = destination.getParentFile(); - + // create parent folder if necessary if (mkdirs && !destinationFolder.isDirectory() && !destinationFolder.mkdirs()) { throw new IOException("Failed to create folder: " + destinationFolder); } - + return destination; } - - + + public static File createRelativeSymlink(File link, File target, boolean relativize) throws IOException { + if (relativize) { + try { + target = link.getParentFile().toPath().relativize(target.toPath()).toFile(); + } catch (Throwable e) { + // unable to relativize link target + } + } + + // create symlink via NIO.2 + return java.nio.file.Files.createSymbolicLink(link.toPath(), target.toPath()).toFile(); + } + public static boolean delete(File file) { // delete files or files return org.apache.commons.io.FileUtils.deleteQuietly(file); } - - + public static byte[] readFile(File source) throws IOException { InputStream in = new FileInputStream(source); - + try { long size = source.length(); if (size < 0 || size > Integer.MAX_VALUE) { throw new IllegalArgumentException("Unable to read file: " + source); } - + byte[] data = new byte[(int) size]; - + int position = 0; int read = 0; - + while (position < data.length && (read = in.read(data, position, data.length - position)) >= 0) { position += read; } - + return data; } finally { in.close(); } } - - + public static String readAll(Reader source) throws IOException { StringBuilder text = new StringBuilder(); char[] buffer = new char[2048]; - + int read = 0; while ((read = source.read(buffer)) >= 0) { text.append(buffer, 0, read); } - + return text.toString(); } - - + public static void writeFile(ByteBuffer data, File destination) throws IOException { FileChannel fileChannel = new FileOutputStream(destination).getChannel(); - + try { fileChannel.write(data); } finally { fileChannel.close(); } } - - + public static Reader createTextReader(File file) throws IOException { CharsetDetector detector = new CharsetDetector(); detector.setDeclaredEncoding("UTF-8"); // small boost for UTF-8 as default encoding detector.setText(new BufferedInputStream(new FileInputStream(file))); - + CharsetMatch charset = detector.detect(); if (charset != null) return charset.getReader(); - + // assume UTF-8 by default return new InputStreamReader(new FileInputStream(file), "UTF-8"); } - - + public static String getText(ByteBuffer data) throws IOException { CharsetDetector detector = new CharsetDetector(); detector.setDeclaredEncoding("UTF-8"); // small boost for UTF-8 as default encoding detector.setText(new ByteBufferInputStream(data)); - + CharsetMatch charset = detector.detect(); if (charset != null) { try { @@ -184,11 +186,11 @@ public final class FileUtilities { throw new IOException("Failed to read text", e); } } - + // assume UTF-8 by default return Charset.forName("UTF-8").decode(data).toString(); } - + /** * Pattern used for matching file extensions. * @@ -196,35 +198,31 @@ public final class FileUtilities { */ public static final Pattern EXTENSION = Pattern.compile("(?<=.[.])\\p{Alnum}+$"); public static final String UNC_PREFIX = "\\\\"; - - + public static String getExtension(File file) { if (file.isDirectory()) return null; - + return getExtension(file.getName()); } - - + public static String getExtension(String name) { Matcher matcher = EXTENSION.matcher(name); - + if (matcher.find()) { // extension without leading '.' return matcher.group(); } - + // no extension return null; } - - + public static boolean hasExtension(File file, String... extensions) { // avoid native call for speed, if possible return hasExtension(file.getName(), extensions) && !file.isDirectory(); } - - + public static boolean hasExtension(String filename, String... extensions) { for (String it : extensions) { if (filename.length() - it.length() >= 2 && filename.charAt(filename.length() - it.length() - 1) == '.') { @@ -234,78 +232,70 @@ public final class FileUtilities { } } } - + return false; } - - + public static String getNameWithoutExtension(String name) { Matcher matcher = EXTENSION.matcher(name); - + if (matcher.find()) { return name.substring(0, matcher.start() - 1); } - + // no extension, return given name return name; } - - + public static String getName(File file) { if (file.getName().isEmpty() || UNC_PREFIX.equals(file.getParent())) return getFolderName(file); - + return getNameWithoutExtension(file.getName()); } - - + public static String getFolderName(File file) { if (UNC_PREFIX.equals(file.getParent())) return file.toString(); - + if (file.getName().length() > 0) return file.getName(); - + // file might be a drive (only has a path, but no name) return replacePathSeparators(file.toString(), ""); } - - + public static boolean isDerived(File derivate, File prime) { return isDerived(getName(derivate), prime); } - - + public static boolean isDerived(String derivate, File prime) { String base = getName(prime).trim().toLowerCase(); derivate = derivate.trim().toLowerCase(); return derivate.startsWith(base); } - - + public static boolean isDerivedByExtension(File derivate, File prime) { return isDerivedByExtension(getName(derivate), prime); } - - + public static boolean isDerivedByExtension(String derivate, File prime) { String base = getName(prime).trim().toLowerCase(); derivate = derivate.trim().toLowerCase(); - + if (derivate.equals(base)) return true; - + while (derivate.length() > base.length() && getExtension(derivate) != null) { derivate = getNameWithoutExtension(derivate); - + if (derivate.equals(base)) return true; } - + return false; } - - + public static boolean containsOnly(Collection files, FileFilter filter) { if (files.isEmpty()) { return false; @@ -316,11 +306,10 @@ public final class FileUtilities { } return true; } - - + public static List filter(Iterable files, FileFilter... filters) { List accepted = new ArrayList(); - + for (File file : files) { for (FileFilter filter : filters) { if (filter.accept(file)) { @@ -329,19 +318,17 @@ public final class FileUtilities { } } } - + return accepted; } - - + public static FileFilter not(FileFilter filter) { return new NotFileFilter(filter); } - - + public static List flatten(Iterable roots, int maxDepth, boolean listHiddenFiles) { List files = new ArrayList(); - + // unfold/flatten file tree for (File root : roots) { if (root.isDirectory()) { @@ -350,28 +337,25 @@ public final class FileUtilities { files.add(root); } } - + return files; } - - + public static List listPath(File file) { return listPathTail(file, Integer.MAX_VALUE); } - - + public static List listPathTail(File file, int tailSize) { LinkedList nodes = new LinkedList(); - + File node = file; for (int i = 0; node != null && i < tailSize && !UNC_PREFIX.equals(node.toString()); i++, node = node.getParentFile()) { nodes.addFirst(node); } - + return nodes; } - - + public static File getRelativePathTail(File file, int tailSize) { File f = null; for (File it : listPathTail(file, tailSize)) { @@ -381,28 +365,26 @@ public final class FileUtilities { } return f; } - - + public static List listFiles(Iterable folders, int maxDepth, boolean listHiddenFiles) { List files = new ArrayList(); - + // collect files from directory tree for (File folder : folders) { listFiles(folder, 0, files, maxDepth, listHiddenFiles); } - + return files; } - - + private static void listFiles(File folder, int depth, List files, int maxDepth, boolean listHiddenFiles) { if (depth > maxDepth) return; - + for (File file : folder.listFiles()) { if (!listHiddenFiles && file.isHidden()) // ignore hidden files continue; - + if (file.isDirectory()) { listFiles(file, depth + 1, files, maxDepth, listHiddenFiles); } else { @@ -410,111 +392,104 @@ public final class FileUtilities { } } } - - + public static SortedMap> mapByFolder(Iterable files) { SortedMap> map = new TreeMap>(); - + for (File file : files) { File key = file.getParentFile(); if (key == null) { throw new IllegalArgumentException("Parent is null: " + file); } - + List valueList = map.get(key); if (valueList == null) { valueList = new ArrayList(); map.put(key, valueList); } - + valueList.add(file); } - + return map; } - - + public static Map> mapByExtension(Iterable files) { Map> map = new HashMap>(); - + for (File file : files) { String key = getExtension(file); if (key != null) { key = key.toLowerCase(); } - + List valueList = map.get(key); if (valueList == null) { valueList = new ArrayList(); map.put(key, valueList); } - + valueList.add(file); } - + return map; } - + /** * Invalid file name characters: \, /, :, *, ?, ", <, >, |, \r and \n */ public static final Pattern ILLEGAL_CHARACTERS = Pattern.compile("[\\\\/:*?\"<>|\\r\\n]|[. ]+$"); - - + /** * Strip file name of invalid characters * - * @param filename original filename + * @param filename + * original filename * @return valid file name stripped of invalid characters */ public static String validateFileName(CharSequence filename) { // strip invalid characters from file name return ILLEGAL_CHARACTERS.matcher(filename).replaceAll("").trim(); } - - + public static boolean isInvalidFileName(CharSequence filename) { // check if file name contains any illegal characters return ILLEGAL_CHARACTERS.matcher(filename).find(); } - - + public static File validateFileName(File file) { // windows drives (e.g. c:, d:, etc.) are never invalid because name will be an empty string if (!isInvalidFileName(file.getName())) return file; - + // validate file name only return new File(file.getParentFile(), validateFileName(file.getName())); } - - + public static File validateFilePath(File path) { Iterator nodes = listPath(path).iterator(); - + // initialize with root node, keep original root object if possible (so we don't loose the drive on windows) File validatedPath = validateFileName(nodes.next()); - + // validate the rest of the path while (nodes.hasNext()) { validatedPath = new File(validatedPath, validateFileName(nodes.next().getName())); } - + return validatedPath; } - - + public static boolean isInvalidFilePath(File path) { // check if file name contains any illegal characters for (File node = path; node != null; node = node.getParentFile()) { if (isInvalidFileName(node.getName())) return true; } - + return false; } - - + public static String normalizePathSeparators(String path) { // special handling for UNC paths if (path.startsWith(UNC_PREFIX) && path.length() > 2) { @@ -522,34 +497,30 @@ public final class FileUtilities { } return path.replace('\\', '/'); } - - + public static String replacePathSeparators(CharSequence path) { return replacePathSeparators(path, " "); } - - + public static String replacePathSeparators(CharSequence path, String replacement) { return Pattern.compile("\\s*[\\\\/]+\\s*").matcher(path).replaceAll(replacement); } - - + public static String getXmlString(Document dom) throws TransformerException { Transformer tr = TransformerFactory.newInstance().newTransformer(); tr.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); tr.setOutputProperty(OutputKeys.INDENT, "yes"); - + // create string from dom StringWriter buffer = new StringWriter(); tr.transform(new DOMSource(dom), new StreamResult(buffer)); return buffer.toString(); } - + public static final long KILO = 1024; public static final long MEGA = KILO * 1024; public static final long GIGA = MEGA * 1024; - - + public static String formatSize(long size) { if (size >= MEGA) return String.format("%,d MB", size / MEGA); @@ -558,121 +529,105 @@ public final class FileUtilities { else return String.format("%,d Byte", size); } - + public static final FileFilter FOLDERS = new FileFilter() { - + @Override public boolean accept(File file) { return file.isDirectory(); } }; - + public static final FileFilter FILES = new FileFilter() { - + @Override public boolean accept(File file) { return file.isFile(); } }; - + public static final FileFilter TEMPORARY = new FileFilter() { - + private final String tmpdir = System.getProperty("java.io.tmpdir"); - - + @Override public boolean accept(File file) { return file.getAbsolutePath().startsWith(tmpdir); } }; - - + public static class ParentFilter implements FileFilter { - + private final File folder; - - + public ParentFilter(File folder) { this.folder = folder; } - - + @Override public boolean accept(File file) { return listPath(file).contains(folder); } } - - + public static class ExtensionFileFilter implements FileFilter { - + private final String[] extensions; - - + public ExtensionFileFilter(String... extensions) { this.extensions = extensions; } - - + public ExtensionFileFilter(Collection extensions) { this.extensions = extensions.toArray(new String[0]); } - - + @Override public boolean accept(File file) { return hasExtension(file, extensions); } - - + public boolean accept(String name) { return hasExtension(name, extensions); } - - + public boolean acceptExtension(String extension) { for (String other : extensions) { if (other.equalsIgnoreCase(extension)) return true; } - + return false; } - - + public String extension() { return extensions[0]; } - - + public String[] extensions() { return extensions.clone(); } } - - + public static class NotFileFilter implements FileFilter { - + public FileFilter filter; - - + public NotFileFilter(FileFilter filter) { this.filter = filter; } - - + @Override public boolean accept(File file) { return !filter.accept(file); } } - - + /** * Dummy constructor to prevent instantiation. */ private FileUtilities() { throw new UnsupportedOperationException(); } - + }