Skip to content

Commit

Permalink
[FS] Speed-up LocalFile.copy() by using Java-NIO's Files.copy()
Browse files Browse the repository at this point in the history
Using Files.copy() can be significantly faster than reading the content
from a source InputStream and writing it to a target OutputStream.

The only drawback of using using Files.copy() is that continuous
progress reporting during the copy operation is not possible. For small
files (which are copied in short time) this is not really a relevant and
for large files the performance gain outweighs the loss in progress
information.
  • Loading branch information
HannesWell committed Jul 21, 2024
1 parent 824a163 commit 6c011dd
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public class Messages extends NLS {
public static String failedCreateWrongType;
public static String failedCreateAccessDenied;
public static String failedMove;
public static String failedCopy;
public static String failedReadDuringWrite;
public static String fileExists;
public static String fileNotFound;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@
import java.io.OutputStream;
import java.net.URI;
import java.nio.file.AccessDeniedException;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
Expand Down Expand Up @@ -129,26 +131,45 @@ public String[] childNames(int options, IProgressMonitor monitor) {

@Override
public void copy(IFileStore destFile, int options, IProgressMonitor monitor) throws CoreException {
if (destFile instanceof LocalFile) {
File source = file;
File destination = ((LocalFile) destFile).file;
if (destFile instanceof LocalFile destination) {
//handle case variants on a case-insensitive OS, or copying between
//two equivalent files in an environment that supports symbolic links.
//in these nothing needs to be copied (and doing so would likely lose data)
try {
if (isSameFile(source, destination)) {
if (isSameFile(this.file, destination.file)) {
//nothing to do
return;
}
} catch (IOException e) {
String message = NLS.bind(Messages.couldNotRead, source.getAbsolutePath());
String message = NLS.bind(Messages.couldNotRead, this.file.getAbsolutePath());
Policy.error(EFS.ERROR_READ, message, e);
}
}
//fall through to super implementation
super.copy(destFile, options, monitor);
}

private static final CopyOption[] NO_OVERWRITE = {StandardCopyOption.COPY_ATTRIBUTES};
private static final CopyOption[] OVERWRITE_EXISTING = {StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING};

@Override
protected void copyFile(IFileInfo sourceInfo, IFileStore destination, int options, IProgressMonitor monitor) throws CoreException {
if (destination instanceof LocalFile target) {
try {
boolean overwrite = (options & EFS.OVERWRITE) != 0;
Files.copy(this.file.toPath(), target.file.toPath(), overwrite ? OVERWRITE_EXISTING : NO_OVERWRITE);
} catch (FileAlreadyExistsException e) {
Policy.error(EFS.ERROR_EXISTS, NLS.bind(Messages.fileExists, target.filePath, e));
} catch (IOException e) {
Policy.error(EFS.ERROR_WRITE, NLS.bind(Messages.failedCopy, this.filePath, target.filePath), e);
} finally {
IProgressMonitor.done(monitor);
}
} else {
super.copyFile(sourceInfo, destination, options, monitor);
}
}

@Override
public void delete(int options, IProgressMonitor monitor) throws CoreException {
if (monitor == null)
Expand Down Expand Up @@ -296,7 +317,7 @@ private IStatus internalDelete(File target, String pathToDelete, IProgressMonito
String message = fetchInfo().getAttribute(EFS.ATTRIBUTE_READ_ONLY) //
? Messages.couldnotDeleteReadOnly

// This is the worst-case scenario: something failed but we don't know what. The children were
// This is the worst-case scenario: something failed but we don't know what. The children were
// deleted successfully and the directory is NOT read-only... there's nothing else to report.
: Messages.couldnotDelete;

Expand Down Expand Up @@ -439,7 +460,7 @@ private boolean isSameFile(File source, File destination) throws IOException {
// avoid NoSuchFileException for performance reasons
return false;
}
// isSameFile is faster then using getCanonicalPath
// isSameFile is faster then using getCanonicalPath
return java.nio.file.Files.isSameFile(source.toPath(), destination.toPath());
} catch (NoSuchFileException e) {
// ignore - it is the normal case that the destination does not exist.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ deleteProblem = Problems encountered while deleting files.
deleting = Deleting: {0}.
failedCreateAccessDenied=Cannot create file, access denied: {0}.
failedCreateWrongType=Cannot create file because existing file of wrong type exists: {0}.
failedMove = Critical failure moving: {0} to: {1}. Content is lost.
failedMove = Critical failure moving '{0}' to '{1}'. Content is lost.
failedCopy = Could not copy file '{0}' to '{1}'
failedReadDuringWrite = Could not read from source when writing file: {0}
fileExists = File already exists on disk: {0}.
fileNotFound = File not found: {0}.
Expand Down

0 comments on commit 6c011dd

Please sign in to comment.