Reputation: 1061
I am writing a little tool for "normalizing" file and directory names to make them safe for portability, interoperability and general reduction of stupid and painful errors due to file names with non-ASCII characters, spaces and other evil characters etc. (see this SuperUser answer for a concise summary of the few truly safe characters).
To do that, I want to rename an file/directory with an unsafe name to a safe name, including renaming all parent directories accordingly. Shouldn't be a problem, even with java.io
that I need to use; however I cannot seem to rename directories containing other directories (containing files works fine).
This is my method:
public static File renameSafe(File unsafe) throws IOException {
if (!unsafe.exists()) {
throw new FileNotFoundException(unsafe.toString());
}
// create full "safe" path
File safe = toSafeFile(unsafe);
File origSafe = safe;
while (unsafe != null) {
// rename the lowest unsafe level
File unsafeParent = unsafe.getParentFile();
File safeTarget = new File(unsafeParent, safe.getName());
if (!unsafe.renameTo(safeTarget)) {
throw new IOException("Could not rename " + unsafe + " to " + safe);
}
unsafe = unsafeParent;
safe = safe.getParentFile();
}
return origSafe;
}
When I run this main method:
public static void main(String[] args) throws IOException {
File file = new File("\\test\\-bad.folder NAME ÄÖÜßéÂÌ\\.bad folder 2\\test filename-ÄÖÜ.txt");
System.out.println(file);
System.out.println(toSafeFileName(file));
renameSafe(file);
}
I get the following output:
\test\-bad.folder NAME ÄÖÜßéÂÌ\.bad folder 2\test filename-ÄÖÜ.txt
\test\_bad_folder_name_aou-eai\_bad_folder_2\test_filename-aou.txt
Exception in thread "main" java.io.IOException: Could not rename \test\-bad.folder NAME ÄÖÜßéÂÌ to \test\_bad_folder_name_aou-eai
at SafeFileName.renameSafe(SafeFileName.java:77)
at SafeFileName.main(SafeFileName.java:90)
The first two levels, everything is renamed as is; reaching the leaf file's grandparent, renameTo()
suddenly returns false
.
So my question is, what am I doing wrong? I tried with other file names (including with 100% input file names), but it always seems that File.renameTo()
can not seem to rename directories containing other directories. However, my personal bet would be some stupidity of myself, paired with some obscure API details.
Okay, here's the SSCCE:
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
public class RenameTest
{
public static File toSafeFileName(File file) {
File safe = new File(file.getName().replace("bad", "good"));
for (;;) {
file = file.getParentFile();
if (file == null) {
return safe;
}
if (file.toString().equals(File.separator)) {
safe = new File("", safe.getPath());
} else {
safe = new File(
file.getName().replace("bad", "good"),
safe.getPath());
}
}
}
public static File renameSafe(File unsafe) throws IOException {
if (!unsafe.exists()) {
throw new FileNotFoundException(unsafe.toString());
}
File safe = toSafeFileName(unsafe);
File origSafe = safe;
while (unsafe != null && !unsafe.equals(safe)) {
File unsafeParent = unsafe.getParentFile();
File safeTarget = new File(unsafeParent, safe.getName());
System.out.print(unsafe + " -> " + safeTarget + "? ");
if (unsafe.renameTo(safeTarget)) {
System.out.println("OK.");
} else {
System.out.println("Error!");
throw new IOException();
}
unsafe = unsafeParent;
safe = safe.getParentFile();
}
return origSafe;
}
public static void main(String[] args) throws IOException {
File unsafePath = new File("\\test\\bad1\\bad2\\bad.txt");
if (!unsafePath.getParentFile().mkdirs()) {
throw new IOException("can't create " + unsafePath.getParentFile());
}
if (!unsafePath.createNewFile()) {
throw new IOException("can't create dummy file");
}
File safePath = renameSafe(unsafePath);
if (safePath.exists()) {
System.out.println("Renamed " + unsafePath + " to " + safePath);
} else {
throw new IOException("Safe path " + safePath + " does not exist.");
}
}
}
On first run, with a clean D:\test
directory, everything runs fine:
\test\bad1\bad2\bad.txt -> \test\bad1\bad2\good.txt? OK.
\test\bad1\bad2 -> \test\bad1\good2? OK.
\test\bad1 -> \test\good1? OK.
Renamed \test\bad1\bad2\bad.txt to \test\good1\good2\good.txt
So OK, Java can rename directories containing directories.
BUT, on the second run (without cleaning D:\test
first), I get the following:
\test\bad1\bad2\bad.txt -> \test\bad1\bad2\good.txt? OK.
\test\bad1\bad2 -> \test\bad1\good2? OK.
\test\bad1 -> \test\good1? Error!
Exception in thread "main" java.io.IOException
at funky.core.io.RenameTest2.renameSafe(RenameTest2.java:46)
at funky.core.io.RenameTest2.main(RenameTest2.java:65)
So it seems that the problems really was I didn't clean up my test directory, and that Java can't rename a directory to an already existing directory -- as the good1
was already created on the first run... As this is stated in the renameTo()
API, this was indeed just my own stupidity. Sorry for wasting your time.
Upvotes: 1
Views: 415
Reputation: 347234
To me, this...
File unsafeParent = unsafe.getParentFile();
File safeTarget = new File(unsafeParent, safe.getName());
So, using \test\-bad.folder NAME ÄÖÜßéÂÌ\.bad folder 2\test filename-ÄÖÜ.txt
as the base path, unsafeParent
would equal \test\-bad.folder NAME ÄÖÜßéÂÌ\.bad folder 2
, but safeTarget
is now unsafeParent + safe.getName()
, so you're not trying to rename the directory, but a file in unsafeParent
named safe.getName()
...
Instead of working bottom up, work top down. This means you can rename the directory and using the renamed reference, list the files in it and process them.
Upvotes: 2