* symlink using relative link targets if possible but default to absolute paths for cross-volume absolute symlinks if necessary

This commit is contained in:
Reinhard Pointner 2013-08-15 10:51:39 +00:00
parent cf11cfecb0
commit be22317f41
2 changed files with 181 additions and 232 deletions

View File

@ -1,13 +1,10 @@
package net.sourceforge.filebot; package net.sourceforge.filebot;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import net.sourceforge.tuned.FileUtilities; import net.sourceforge.tuned.FileUtilities;
public enum StandardRenameAction implements RenameAction { public enum StandardRenameAction implements RenameAction {
MOVE { MOVE {
@ -35,7 +32,7 @@ public enum StandardRenameAction implements RenameAction {
// move file and the create a symlink to the new location via NIO.2 // move file and the create a symlink to the new location via NIO.2
try { try {
java.nio.file.Files.move(from.toPath(), destionation.toPath()); 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) { } catch (LinkageError e) {
throw new Exception("Unsupported Operation: move, createSymbolicLink"); throw new Exception("Unsupported Operation: move, createSymbolicLink");
} }
@ -52,12 +49,10 @@ public enum StandardRenameAction implements RenameAction {
// create symlink via NIO.2 // create symlink via NIO.2
try { try {
java.nio.file.Files.createSymbolicLink(destionation.toPath(), from.toPath()); return FileUtilities.createRelativeSymlink(destionation, from, true);
} catch (LinkageError e) { } catch (LinkageError e) {
throw new Exception("Unsupported Operation: createSymbolicLink"); throw new Exception("Unsupported Operation: createSymbolicLink");
} }
return destionation;
} }
}, },
@ -117,7 +112,6 @@ public enum StandardRenameAction implements RenameAction {
} }
} }
public static StandardRenameAction forName(String action) { public static StandardRenameAction forName(String action) {
for (StandardRenameAction it : values()) { for (StandardRenameAction it : values()) {
if (it.name().equalsIgnoreCase(action)) if (it.name().equalsIgnoreCase(action))

View File

@ -1,7 +1,5 @@
package net.sourceforge.tuned; package net.sourceforge.tuned;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
@ -40,7 +38,6 @@ import org.w3c.dom.Document;
import com.ibm.icu.text.CharsetDetector; import com.ibm.icu.text.CharsetDetector;
import com.ibm.icu.text.CharsetMatch; import com.ibm.icu.text.CharsetMatch;
public final class FileUtilities { public final class FileUtilities {
public static File moveRename(File source, File destination) throws IOException { public static File moveRename(File source, File destination) throws IOException {
@ -62,7 +59,6 @@ public final class FileUtilities {
return destination; return destination;
} }
public static File copyAs(File source, File destination) throws IOException { public static File copyAs(File source, File destination) throws IOException {
// resolve destination // resolve destination
destination = resolveDestination(source, destination, true); destination = resolveDestination(source, destination, true);
@ -82,7 +78,6 @@ public final class FileUtilities {
return destination; return destination;
} }
public static File resolveDestination(File source, File destination, boolean mkdirs) throws IOException { public static File resolveDestination(File source, File destination, boolean mkdirs) throws IOException {
// resolve destination // resolve destination
if (!destination.isAbsolute()) { if (!destination.isAbsolute()) {
@ -101,13 +96,24 @@ public final class FileUtilities {
return destination; 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) { public static boolean delete(File file) {
// delete files or files // delete files or files
return org.apache.commons.io.FileUtils.deleteQuietly(file); return org.apache.commons.io.FileUtils.deleteQuietly(file);
} }
public static byte[] readFile(File source) throws IOException { public static byte[] readFile(File source) throws IOException {
InputStream in = new FileInputStream(source); InputStream in = new FileInputStream(source);
@ -132,7 +138,6 @@ public final class FileUtilities {
} }
} }
public static String readAll(Reader source) throws IOException { public static String readAll(Reader source) throws IOException {
StringBuilder text = new StringBuilder(); StringBuilder text = new StringBuilder();
char[] buffer = new char[2048]; char[] buffer = new char[2048];
@ -145,7 +150,6 @@ public final class FileUtilities {
return text.toString(); return text.toString();
} }
public static void writeFile(ByteBuffer data, File destination) throws IOException { public static void writeFile(ByteBuffer data, File destination) throws IOException {
FileChannel fileChannel = new FileOutputStream(destination).getChannel(); FileChannel fileChannel = new FileOutputStream(destination).getChannel();
@ -156,7 +160,6 @@ public final class FileUtilities {
} }
} }
public static Reader createTextReader(File file) throws IOException { public static Reader createTextReader(File file) throws IOException {
CharsetDetector detector = new CharsetDetector(); CharsetDetector detector = new CharsetDetector();
detector.setDeclaredEncoding("UTF-8"); // small boost for UTF-8 as default encoding detector.setDeclaredEncoding("UTF-8"); // small boost for UTF-8 as default encoding
@ -170,7 +173,6 @@ public final class FileUtilities {
return new InputStreamReader(new FileInputStream(file), "UTF-8"); return new InputStreamReader(new FileInputStream(file), "UTF-8");
} }
public static String getText(ByteBuffer data) throws IOException { public static String getText(ByteBuffer data) throws IOException {
CharsetDetector detector = new CharsetDetector(); CharsetDetector detector = new CharsetDetector();
detector.setDeclaredEncoding("UTF-8"); // small boost for UTF-8 as default encoding detector.setDeclaredEncoding("UTF-8"); // small boost for UTF-8 as default encoding
@ -197,7 +199,6 @@ public final class FileUtilities {
public static final Pattern EXTENSION = Pattern.compile("(?<=.[.])\\p{Alnum}+$"); public static final Pattern EXTENSION = Pattern.compile("(?<=.[.])\\p{Alnum}+$");
public static final String UNC_PREFIX = "\\\\"; public static final String UNC_PREFIX = "\\\\";
public static String getExtension(File file) { public static String getExtension(File file) {
if (file.isDirectory()) if (file.isDirectory())
return null; return null;
@ -205,7 +206,6 @@ public final class FileUtilities {
return getExtension(file.getName()); return getExtension(file.getName());
} }
public static String getExtension(String name) { public static String getExtension(String name) {
Matcher matcher = EXTENSION.matcher(name); Matcher matcher = EXTENSION.matcher(name);
@ -218,13 +218,11 @@ public final class FileUtilities {
return null; return null;
} }
public static boolean hasExtension(File file, String... extensions) { public static boolean hasExtension(File file, String... extensions) {
// avoid native call for speed, if possible // avoid native call for speed, if possible
return hasExtension(file.getName(), extensions) && !file.isDirectory(); return hasExtension(file.getName(), extensions) && !file.isDirectory();
} }
public static boolean hasExtension(String filename, String... extensions) { public static boolean hasExtension(String filename, String... extensions) {
for (String it : extensions) { for (String it : extensions) {
if (filename.length() - it.length() >= 2 && filename.charAt(filename.length() - it.length() - 1) == '.') { if (filename.length() - it.length() >= 2 && filename.charAt(filename.length() - it.length() - 1) == '.') {
@ -238,7 +236,6 @@ public final class FileUtilities {
return false; return false;
} }
public static String getNameWithoutExtension(String name) { public static String getNameWithoutExtension(String name) {
Matcher matcher = EXTENSION.matcher(name); Matcher matcher = EXTENSION.matcher(name);
@ -250,7 +247,6 @@ public final class FileUtilities {
return name; return name;
} }
public static String getName(File file) { public static String getName(File file) {
if (file.getName().isEmpty() || UNC_PREFIX.equals(file.getParent())) if (file.getName().isEmpty() || UNC_PREFIX.equals(file.getParent()))
return getFolderName(file); return getFolderName(file);
@ -258,7 +254,6 @@ public final class FileUtilities {
return getNameWithoutExtension(file.getName()); return getNameWithoutExtension(file.getName());
} }
public static String getFolderName(File file) { public static String getFolderName(File file) {
if (UNC_PREFIX.equals(file.getParent())) if (UNC_PREFIX.equals(file.getParent()))
return file.toString(); return file.toString();
@ -270,24 +265,20 @@ public final class FileUtilities {
return replacePathSeparators(file.toString(), ""); return replacePathSeparators(file.toString(), "");
} }
public static boolean isDerived(File derivate, File prime) { public static boolean isDerived(File derivate, File prime) {
return isDerived(getName(derivate), prime); return isDerived(getName(derivate), prime);
} }
public static boolean isDerived(String derivate, File prime) { public static boolean isDerived(String derivate, File prime) {
String base = getName(prime).trim().toLowerCase(); String base = getName(prime).trim().toLowerCase();
derivate = derivate.trim().toLowerCase(); derivate = derivate.trim().toLowerCase();
return derivate.startsWith(base); return derivate.startsWith(base);
} }
public static boolean isDerivedByExtension(File derivate, File prime) { public static boolean isDerivedByExtension(File derivate, File prime) {
return isDerivedByExtension(getName(derivate), prime); return isDerivedByExtension(getName(derivate), prime);
} }
public static boolean isDerivedByExtension(String derivate, File prime) { public static boolean isDerivedByExtension(String derivate, File prime) {
String base = getName(prime).trim().toLowerCase(); String base = getName(prime).trim().toLowerCase();
derivate = derivate.trim().toLowerCase(); derivate = derivate.trim().toLowerCase();
@ -305,7 +296,6 @@ public final class FileUtilities {
return false; return false;
} }
public static boolean containsOnly(Collection<File> files, FileFilter filter) { public static boolean containsOnly(Collection<File> files, FileFilter filter) {
if (files.isEmpty()) { if (files.isEmpty()) {
return false; return false;
@ -317,7 +307,6 @@ public final class FileUtilities {
return true; return true;
} }
public static List<File> filter(Iterable<File> files, FileFilter... filters) { public static List<File> filter(Iterable<File> files, FileFilter... filters) {
List<File> accepted = new ArrayList<File>(); List<File> accepted = new ArrayList<File>();
@ -333,12 +322,10 @@ public final class FileUtilities {
return accepted; return accepted;
} }
public static FileFilter not(FileFilter filter) { public static FileFilter not(FileFilter filter) {
return new NotFileFilter(filter); return new NotFileFilter(filter);
} }
public static List<File> flatten(Iterable<File> roots, int maxDepth, boolean listHiddenFiles) { public static List<File> flatten(Iterable<File> roots, int maxDepth, boolean listHiddenFiles) {
List<File> files = new ArrayList<File>(); List<File> files = new ArrayList<File>();
@ -354,12 +341,10 @@ public final class FileUtilities {
return files; return files;
} }
public static List<File> listPath(File file) { public static List<File> listPath(File file) {
return listPathTail(file, Integer.MAX_VALUE); return listPathTail(file, Integer.MAX_VALUE);
} }
public static List<File> listPathTail(File file, int tailSize) { public static List<File> listPathTail(File file, int tailSize) {
LinkedList<File> nodes = new LinkedList<File>(); LinkedList<File> nodes = new LinkedList<File>();
@ -371,7 +356,6 @@ public final class FileUtilities {
return nodes; return nodes;
} }
public static File getRelativePathTail(File file, int tailSize) { public static File getRelativePathTail(File file, int tailSize) {
File f = null; File f = null;
for (File it : listPathTail(file, tailSize)) { for (File it : listPathTail(file, tailSize)) {
@ -382,7 +366,6 @@ public final class FileUtilities {
return f; return f;
} }
public static List<File> listFiles(Iterable<File> folders, int maxDepth, boolean listHiddenFiles) { public static List<File> listFiles(Iterable<File> folders, int maxDepth, boolean listHiddenFiles) {
List<File> files = new ArrayList<File>(); List<File> files = new ArrayList<File>();
@ -394,7 +377,6 @@ public final class FileUtilities {
return files; return files;
} }
private static void listFiles(File folder, int depth, List<File> files, int maxDepth, boolean listHiddenFiles) { private static void listFiles(File folder, int depth, List<File> files, int maxDepth, boolean listHiddenFiles) {
if (depth > maxDepth) if (depth > maxDepth)
return; return;
@ -411,7 +393,6 @@ public final class FileUtilities {
} }
} }
public static SortedMap<File, List<File>> mapByFolder(Iterable<File> files) { public static SortedMap<File, List<File>> mapByFolder(Iterable<File> files) {
SortedMap<File, List<File>> map = new TreeMap<File, List<File>>(); SortedMap<File, List<File>> map = new TreeMap<File, List<File>>();
@ -433,7 +414,6 @@ public final class FileUtilities {
return map; return map;
} }
public static Map<String, List<File>> mapByExtension(Iterable<File> files) { public static Map<String, List<File>> mapByExtension(Iterable<File> files) {
Map<String, List<File>> map = new HashMap<String, List<File>>(); Map<String, List<File>> map = new HashMap<String, List<File>>();
@ -460,11 +440,11 @@ public final class FileUtilities {
*/ */
public static final Pattern ILLEGAL_CHARACTERS = Pattern.compile("[\\\\/:*?\"<>|\\r\\n]|[. ]+$"); public static final Pattern ILLEGAL_CHARACTERS = Pattern.compile("[\\\\/:*?\"<>|\\r\\n]|[. ]+$");
/** /**
* Strip file name of invalid characters * Strip file name of invalid characters
* *
* @param filename original filename * @param filename
* original filename
* @return valid file name stripped of invalid characters * @return valid file name stripped of invalid characters
*/ */
public static String validateFileName(CharSequence filename) { public static String validateFileName(CharSequence filename) {
@ -472,13 +452,11 @@ public final class FileUtilities {
return ILLEGAL_CHARACTERS.matcher(filename).replaceAll("").trim(); return ILLEGAL_CHARACTERS.matcher(filename).replaceAll("").trim();
} }
public static boolean isInvalidFileName(CharSequence filename) { public static boolean isInvalidFileName(CharSequence filename) {
// check if file name contains any illegal characters // check if file name contains any illegal characters
return ILLEGAL_CHARACTERS.matcher(filename).find(); return ILLEGAL_CHARACTERS.matcher(filename).find();
} }
public static File validateFileName(File file) { public static File validateFileName(File file) {
// windows drives (e.g. c:, d:, etc.) are never invalid because name will be an empty string // windows drives (e.g. c:, d:, etc.) are never invalid because name will be an empty string
if (!isInvalidFileName(file.getName())) if (!isInvalidFileName(file.getName()))
@ -488,7 +466,6 @@ public final class FileUtilities {
return new File(file.getParentFile(), validateFileName(file.getName())); return new File(file.getParentFile(), validateFileName(file.getName()));
} }
public static File validateFilePath(File path) { public static File validateFilePath(File path) {
Iterator<File> nodes = listPath(path).iterator(); Iterator<File> nodes = listPath(path).iterator();
@ -503,7 +480,6 @@ public final class FileUtilities {
return validatedPath; return validatedPath;
} }
public static boolean isInvalidFilePath(File path) { public static boolean isInvalidFilePath(File path) {
// check if file name contains any illegal characters // check if file name contains any illegal characters
for (File node = path; node != null; node = node.getParentFile()) { for (File node = path; node != null; node = node.getParentFile()) {
@ -514,7 +490,6 @@ public final class FileUtilities {
return false; return false;
} }
public static String normalizePathSeparators(String path) { public static String normalizePathSeparators(String path) {
// special handling for UNC paths // special handling for UNC paths
if (path.startsWith(UNC_PREFIX) && path.length() > 2) { if (path.startsWith(UNC_PREFIX) && path.length() > 2) {
@ -523,17 +498,14 @@ public final class FileUtilities {
return path.replace('\\', '/'); return path.replace('\\', '/');
} }
public static String replacePathSeparators(CharSequence path) { public static String replacePathSeparators(CharSequence path) {
return replacePathSeparators(path, " "); return replacePathSeparators(path, " ");
} }
public static String replacePathSeparators(CharSequence path, String replacement) { public static String replacePathSeparators(CharSequence path, String replacement) {
return Pattern.compile("\\s*[\\\\/]+\\s*").matcher(path).replaceAll(replacement); return Pattern.compile("\\s*[\\\\/]+\\s*").matcher(path).replaceAll(replacement);
} }
public static String getXmlString(Document dom) throws TransformerException { public static String getXmlString(Document dom) throws TransformerException {
Transformer tr = TransformerFactory.newInstance().newTransformer(); Transformer tr = TransformerFactory.newInstance().newTransformer();
tr.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); tr.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
@ -549,7 +521,6 @@ public final class FileUtilities {
public static final long MEGA = KILO * 1024; public static final long MEGA = KILO * 1024;
public static final long GIGA = MEGA * 1024; public static final long GIGA = MEGA * 1024;
public static String formatSize(long size) { public static String formatSize(long size) {
if (size >= MEGA) if (size >= MEGA)
return String.format("%,d MB", size / MEGA); return String.format("%,d MB", size / MEGA);
@ -579,57 +550,47 @@ public final class FileUtilities {
private final String tmpdir = System.getProperty("java.io.tmpdir"); private final String tmpdir = System.getProperty("java.io.tmpdir");
@Override @Override
public boolean accept(File file) { public boolean accept(File file) {
return file.getAbsolutePath().startsWith(tmpdir); return file.getAbsolutePath().startsWith(tmpdir);
} }
}; };
public static class ParentFilter implements FileFilter { public static class ParentFilter implements FileFilter {
private final File folder; private final File folder;
public ParentFilter(File folder) { public ParentFilter(File folder) {
this.folder = folder; this.folder = folder;
} }
@Override @Override
public boolean accept(File file) { public boolean accept(File file) {
return listPath(file).contains(folder); return listPath(file).contains(folder);
} }
} }
public static class ExtensionFileFilter implements FileFilter { public static class ExtensionFileFilter implements FileFilter {
private final String[] extensions; private final String[] extensions;
public ExtensionFileFilter(String... extensions) { public ExtensionFileFilter(String... extensions) {
this.extensions = extensions; this.extensions = extensions;
} }
public ExtensionFileFilter(Collection<String> extensions) { public ExtensionFileFilter(Collection<String> extensions) {
this.extensions = extensions.toArray(new String[0]); this.extensions = extensions.toArray(new String[0]);
} }
@Override @Override
public boolean accept(File file) { public boolean accept(File file) {
return hasExtension(file, extensions); return hasExtension(file, extensions);
} }
public boolean accept(String name) { public boolean accept(String name) {
return hasExtension(name, extensions); return hasExtension(name, extensions);
} }
public boolean acceptExtension(String extension) { public boolean acceptExtension(String extension) {
for (String other : extensions) { for (String other : extensions) {
if (other.equalsIgnoreCase(extension)) if (other.equalsIgnoreCase(extension))
@ -639,35 +600,29 @@ public final class FileUtilities {
return false; return false;
} }
public String extension() { public String extension() {
return extensions[0]; return extensions[0];
} }
public String[] extensions() { public String[] extensions() {
return extensions.clone(); return extensions.clone();
} }
} }
public static class NotFileFilter implements FileFilter { public static class NotFileFilter implements FileFilter {
public FileFilter filter; public FileFilter filter;
public NotFileFilter(FileFilter filter) { public NotFileFilter(FileFilter filter) {
this.filter = filter; this.filter = filter;
} }
@Override @Override
public boolean accept(File file) { public boolean accept(File file) {
return !filter.accept(file); return !filter.accept(file);
} }
} }
/** /**
* Dummy constructor to prevent instantiation. * Dummy constructor to prevent instantiation.
*/ */