Use NIO2 to implement FileUtils.rename() and expose options
- use NIO2's Files.move() to reimplement rename() - provide a second method accepting CopyOptions which can be used to request atomic move. Change-Id: Ibcf722978e65745218a1ccda45344ca295911659 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
This commit is contained in:
parent
2e5c7c5db4
commit
548ba66a37
|
@ -48,6 +48,8 @@
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
@ -83,4 +85,14 @@ public void testDeleteSymlinkToDirectoryDoesNotDeleteTarget()
|
||||||
assertTrue(dir.exists());
|
assertTrue(dir.exists());
|
||||||
assertTrue(file.exists());
|
assertTrue(file.exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAtomicMove() throws IOException {
|
||||||
|
File src = new File(trash, "src");
|
||||||
|
Files.createFile(src.toPath());
|
||||||
|
File dst = new File(trash, "dst");
|
||||||
|
FileUtils.rename(src, dst, StandardCopyOption.ATOMIC_MOVE);
|
||||||
|
assertFalse(Files.exists(src.toPath()));
|
||||||
|
assertTrue(Files.exists(dst.toPath()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,9 +48,12 @@
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.channels.FileLock;
|
import java.nio.channels.FileLock;
|
||||||
|
import java.nio.file.AtomicMoveNotSupportedException;
|
||||||
|
import java.nio.file.CopyOption;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.LinkOption;
|
import java.nio.file.LinkOption;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.text.Normalizer;
|
import java.text.Normalizer;
|
||||||
import java.text.Normalizer.Form;
|
import java.text.Normalizer.Form;
|
||||||
|
@ -212,30 +215,68 @@ public static void delete(final File f, int options) throws IOException {
|
||||||
*/
|
*/
|
||||||
public static void rename(final File src, final File dst)
|
public static void rename(final File src, final File dst)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
rename(src, dst, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rename a file or folder using the passed {@link CopyOption}s. If the
|
||||||
|
* rename fails and if we are running on a filesystem where it makes sense
|
||||||
|
* to repeat a failing rename then repeat the rename operation up to 9 times
|
||||||
|
* with 100ms sleep time between two calls. Furthermore if the destination
|
||||||
|
* exists and is a directory hierarchy with only directories in it, the
|
||||||
|
* whole directory hierarchy will be deleted. If the target represents a
|
||||||
|
* non-empty directory structure, empty subdirectories within that structure
|
||||||
|
* may or may not be deleted even if the method fails. Furthermore if the
|
||||||
|
* destination exists and is a file then the file will be replaced if
|
||||||
|
* {@link StandardCopyOption#REPLACE_EXISTING} has been set. If
|
||||||
|
* {@link StandardCopyOption#ATOMIC_MOVE} has been set the rename will be
|
||||||
|
* done atomically or fail with an {@link AtomicMoveNotSupportedException}
|
||||||
|
*
|
||||||
|
* @param src
|
||||||
|
* the old file
|
||||||
|
* @param dst
|
||||||
|
* the new file
|
||||||
|
* @param options
|
||||||
|
* options to pass to
|
||||||
|
* {@link Files#move(java.nio.file.Path, java.nio.file.Path, CopyOption...)}
|
||||||
|
* @throws AtomicMoveNotSupportedException
|
||||||
|
* if file cannot be moved as an atomic file system operation
|
||||||
|
* @throws IOException
|
||||||
|
* @since 4.1
|
||||||
|
*/
|
||||||
|
public static void rename(final File src, final File dst,
|
||||||
|
CopyOption... options)
|
||||||
|
throws AtomicMoveNotSupportedException, IOException {
|
||||||
int attempts = FS.DETECTED.retryFailedLockFileCommit() ? 10 : 1;
|
int attempts = FS.DETECTED.retryFailedLockFileCommit() ? 10 : 1;
|
||||||
while (--attempts >= 0) {
|
while (--attempts >= 0) {
|
||||||
if (src.renameTo(dst))
|
|
||||||
return;
|
|
||||||
try {
|
try {
|
||||||
if (!dst.delete())
|
Files.move(src.toPath(), dst.toPath(), options);
|
||||||
delete(dst, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
|
return;
|
||||||
// On *nix there is no try, you do or do not
|
} catch (AtomicMoveNotSupportedException e) {
|
||||||
if (src.renameTo(dst))
|
throw e;
|
||||||
return;
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// ignore and continue retry
|
try {
|
||||||
|
if (!dst.delete()) {
|
||||||
|
delete(dst, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
|
||||||
|
}
|
||||||
|
// On *nix there is no try, you do or do not
|
||||||
|
Files.move(src.toPath(), dst.toPath(), options);
|
||||||
|
return;
|
||||||
|
} catch (IOException e2) {
|
||||||
|
// ignore and continue retry
|
||||||
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Thread.sleep(100);
|
Thread.sleep(100);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
throw new IOException(MessageFormat.format(
|
throw new IOException(
|
||||||
JGitText.get().renameFileFailed, src.getAbsolutePath(),
|
MessageFormat.format(JGitText.get().renameFileFailed,
|
||||||
dst.getAbsolutePath()));
|
src.getAbsolutePath(), dst.getAbsolutePath()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new IOException(MessageFormat.format(
|
throw new IOException(
|
||||||
JGitText.get().renameFileFailed, src.getAbsolutePath(),
|
MessageFormat.format(JGitText.get().renameFileFailed,
|
||||||
dst.getAbsolutePath()));
|
src.getAbsolutePath(), dst.getAbsolutePath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue