Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -870,7 +870,7 @@ public boolean isReadOnly() {
try {
IFileStore fileStore = getFileStore();
if (fileStore != null) {
return fileStore.fetchInfo().getAttribute(EFS.ATTRIBUTE_READ_ONLY);
return fileStore.fetchInfo(EFS.IGNORE_NAME_CASE, null).getAttribute(EFS.ATTRIBUTE_READ_ONLY);
}
} catch (CoreException e) {
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2005, 2013 IBM Corporation and others.
* Copyright (c) 2005, 2026 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
Expand Down Expand Up @@ -105,6 +105,17 @@ public class EFS {
*/
public static final int CACHE = 1 << 12;

/**
* Option flag constant (value 1 &lt;&lt;13) indicating that
* the exact casing of a file's name does not need to be determined.
* <p>
* For case-insensitive file-systems, like on Windows, this can accelerate fetching file information.
* </p>
* @see IFileStore#fetchInfo(int, IProgressMonitor)
* @since 1.12
*/
public static final int IGNORE_NAME_CASE = 1 << 13;

/**
* Attribute constant (value 1 &lt;&lt;1) indicating that a
* file is read only.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,14 @@ public interface IFileStore extends IAdaptable {
* file, the returned info will include the file's name and will return <code>false</code>
* when IFileInfo#exists() is called, but all other information will assume default
* values.
* </p>
* <p>
* The {@link EFS#IGNORE_NAME_CASE} option flag indicates if
* the casing of this file's name on the file-system is determined or not.
* This is only relevant for case-insensitive file-systems, but can accelerate the fetch.
* </p>
*
* @param options bit-wise or of option flag constants (currently only {@link EFS#NONE}
* is applicable).
* @param options bit-wise or of option flag constants ({@link EFS#NONE} or {@link EFS#IGNORE_NAME_CASE}).
* @param monitor a progress monitor, or <code>null</code> if progress
* reporting and cancellation are not desired
* @return A structure containing information about this file.
Expand All @@ -221,6 +226,7 @@ public interface IFileStore extends IAdaptable {
* <li>Problems occurred while contacting the file system.</li>
* </ul>
* @see IFileTree#getFileInfo(IFileStore)
* @see EFS#IGNORE_NAME_CASE
*/
public IFileInfo fetchInfo(int options, IProgressMonitor monitor) throws CoreException;

Expand All @@ -231,7 +237,11 @@ public interface IFileStore extends IAdaptable {
* @since 1.12
*/
public default boolean exists() {
return fetchInfo().exists();
try {
return fetchInfo(EFS.IGNORE_NAME_CASE, null).exists();
} catch (CoreException e) {
return false;
}
}

/**
Expand All @@ -241,7 +251,11 @@ public default boolean exists() {
* @since 1.12
*/
public default boolean isDirectory() {
return fetchInfo().isDirectory();
try {
return fetchInfo(EFS.IGNORE_NAME_CASE, null).isDirectory();
} catch (CoreException e) {
return false;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public IFileStore[] childStores(int options, IProgressMonitor monitor) throws Co
*/
@Override
public void copy(IFileStore destination, int options, IProgressMonitor monitor) throws CoreException {
final IFileInfo sourceInfo = fetchInfo(EFS.NONE, null);
final IFileInfo sourceInfo = fetchInfo(EFS.IGNORE_NAME_CASE, null);
if (sourceInfo.isDirectory()) {
copyDirectory(sourceInfo, destination, options, monitor);
} else {
Expand Down Expand Up @@ -178,7 +178,7 @@ protected void copyFile(IFileInfo sourceInfo, IFileStore destination, int option
Policy.error(EFS.ERROR_WRITE, NLS.bind(Messages.failedCopy, sourcePath), e);
} catch (CoreException e) {
//if we failed to write, try to cleanup the half written file
if (!destination.fetchInfo(0, null).exists()) {
if (!destination.fetchInfo(EFS.IGNORE_NAME_CASE, null).exists()) {
destination.delete(EFS.NONE, null);
}
throw e;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@

import java.io.File;
import java.io.IOException;
import org.eclipse.core.filesystem.*;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.filesystem.provider.FileStore;
import org.eclipse.core.internal.filesystem.local.LocalFile;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.osgi.service.environment.Constants;
import org.eclipse.osgi.util.NLS;

Expand Down Expand Up @@ -93,7 +98,7 @@ public static FileCache getCache() throws CoreException {
public java.io.File cache(IFileStore source, IProgressMonitor monitor) throws CoreException {
try {
SubMonitor subMonitor = SubMonitor.convert(monitor, NLS.bind(Messages.copying, toString()), 3);
IFileInfo myInfo = source.fetchInfo(EFS.NONE, subMonitor.newChild(1));
IFileInfo myInfo = source.fetchInfo(EFS.IGNORE_NAME_CASE, subMonitor.newChild(1));
if (!myInfo.exists()) {
return new File(cacheDir, "Non-Existent-" + System.currentTimeMillis()); //$NON-NLS-1$
}
Expand Down Expand Up @@ -141,7 +146,7 @@ private void clearImmutableFlag(File target) {
} else {
LocalFile lfile = new LocalFile(target);
try {
IFileInfo info = lfile.fetchInfo(EFS.NONE, null);
IFileInfo info = lfile.fetchInfo(EFS.IGNORE_NAME_CASE, null);
if (info.getAttribute(EFS.ATTRIBUTE_IMMUTABLE)) {
info.setAttribute(EFS.ATTRIBUTE_IMMUTABLE, false);
lfile.putInfo(info, EFS.SET_ATTRIBUTES, null);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2005, 2024 IBM Corporation and others.
* Copyright (c) 2005, 2026 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
Expand Down Expand Up @@ -232,7 +232,7 @@ public boolean equals(Object obj) {

@Override
public IFileInfo fetchInfo(int options, IProgressMonitor monitor) {
FileInfo info = LocalFileNativesManager.fetchFileInfo(filePath);
FileInfo info = LocalFileNativesManager.fetchFileInfo(filePath, options);
//natives don't set the file name on all platforms
if (info.getName().isEmpty()) {
String name = file.getName();
Expand Down Expand Up @@ -296,7 +296,7 @@ private static ForkJoinPool createExecutor(int threadCount) {
/* corePoolSize */ 0, //
/* maximumPoolSize */ threadCount, //
/* minimumRunnable */ 0, //
pool -> true, // if maximumPoolSize would be exceeded, don't throw RejectedExecutionException
_ -> true, // if maximumPoolSize would be exceeded, don't throw RejectedExecutionException
/* keepAliveTime */ 1, TimeUnit.MINUTES); // pool terminates 1 thread per
}

Expand Down Expand Up @@ -376,7 +376,7 @@ private IStatus internalDelete(File target, InfiniteProgress infMonitor, Executo
}

// If we got this far, we failed.
String message = fetchInfo().getAttribute(EFS.ATTRIBUTE_READ_ONLY) //
String message = fetchInfo(EFS.IGNORE_NAME_CASE, null).getAttribute(EFS.ATTRIBUTE_READ_ONLY) //
? Messages.couldnotDeleteReadOnly

// This is the worst-case scenario: something failed but we don't know what. The children were
Expand Down Expand Up @@ -499,7 +499,7 @@ public void move(IFileStore destFile, int options, IProgressMonitor monitor) thr
// source exists but destination doesn't so try to copy below
} else {
// destination.exists() returns false for broken links, this has to be handled explicitly
if (!destination.exists() && !destFile.fetchInfo().getAttribute(EFS.ATTRIBUTE_SYMLINK)) {
if (!destination.exists() && !destFile.fetchInfo(EFS.IGNORE_NAME_CASE, null).getAttribute(EFS.ATTRIBUTE_SYMLINK)) {
// neither the source nor the destination exist. this is REALLY bad
String message = NLS.bind(Messages.failedMove, source.getAbsolutePath(), destination.getAbsolutePath());
Policy.error(EFS.ERROR_WRITE, message);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2010, 2016 IBM Corporation and others.
* Copyright (c) 2010, 2026 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
Expand Down Expand Up @@ -88,8 +88,8 @@ public static int getSupportedAttributes() {
return HANDLER.getSupportedAttributes();
}

public static FileInfo fetchFileInfo(String fileName) {
return HANDLER.fetchFileInfo(fileName);
public static FileInfo fetchFileInfo(String fileName, int options) {
return HANDLER.fetchFileInfo(fileName, options);
}

public static boolean putFileInfo(String fileName, IFileInfo info) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2012 IBM Corporation and others.
* Copyright (c) 2012, 2026 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
Expand All @@ -14,6 +14,7 @@
package org.eclipse.core.internal.filesystem.local;

import java.io.File;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.provider.FileInfo;

Expand All @@ -23,7 +24,7 @@
public abstract class NativeHandler {
public abstract int getSupportedAttributes();

public abstract FileInfo fetchFileInfo(String fileName);
public abstract FileInfo fetchFileInfo(String fileName, int options);

public abstract boolean putFileInfo(String fileName, IFileInfo info);

Expand All @@ -38,7 +39,7 @@ public IFileInfo[] listDirectoryAndGetFileInfos(String fileName) {
var directoryContents = listDirectoryNames(fileName);
var result = new IFileInfo[directoryContents.length];
for (int i = 0; i < directoryContents.length; i++) {
result[i] = fetchFileInfo(fileName + File.separator + directoryContents[i]);
result[i] = fetchFileInfo(fileName + File.separator + directoryContents[i], EFS.NONE);
}
return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,19 @@
import java.lang.invoke.MethodHandle;
import java.lang.invoke.VarHandle;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.attribute.DosFileAttributes;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.provider.FileInfo;
Expand Down Expand Up @@ -70,7 +76,7 @@ public int getSupportedAttributes() {
* Depending on the length of the path, this implementation is consequently multiple times, up to a magnitude faster than the mentioned Java API.
*/
@Override
public FileInfo fetchFileInfo(String fileName) {
public FileInfo fetchFileInfo(String fileName, int options) {
FileInfo fileInfo = new FileInfo();

String target = toLongWindowsPath(fileName);
Expand All @@ -83,6 +89,11 @@ public FileInfo fetchFileInfo(String fileName) {
fileInfo.setExists(Files.exists(file));
return fileInfo;
}
if ((options & EFS.IGNORE_NAME_CASE) != 0) {
// Use faster method to read file attributes without determining the real casing of its name
readDosFileAttributesIntoFileInfo(fileInfo, file);
return fileInfo;
}
try (Arena arena = Arena.ofConfined()) {
MemorySegment lpFileName = allocateWideString(target, arena);
@SuppressWarnings("static-access")
Expand Down Expand Up @@ -140,6 +151,25 @@ private static int GetLastError(MemorySegment capturedError) {
return (int) GET_LAST_ERROR_HANDLE.get(capturedError, 0L);
}

private static final IFileInfo[] EMPTY_FILEINFO_ARRAY = {};

@Override
public IFileInfo[] listDirectoryAndGetFileInfos(String fileName) {
Path file = Path.of(fileName);
List<IFileInfo> children = new ArrayList<>();
try (DirectoryStream<Path> directoryContent = Files.newDirectoryStream(file);) {
for (Path child : directoryContent) {
FileInfo fileInfo = new FileInfo();
// The directory stream delivers real names and therefore the faster method can be used
readDosFileAttributesIntoFileInfo(fileInfo, child);
children.add(fileInfo);
}
} catch (IOException e) {
return EMPTY_FILEINFO_ARRAY;
}
return children.toArray(IFileInfo[]::new);
}

/**
* Sets the given {@link IFileInfo} to the given file.
*
Expand Down Expand Up @@ -213,6 +243,38 @@ private static String toLongWindowsPath(String fileName) {
}
}

/**
* Reads the DOS file attributes of the file without determining its real name
* (which may have different letter case, because the Window file system is case-insensitive).
*
* This is significantly faster than using {@code FindFirstFileW()}, which effectively searches a directory
* and requires two native calls.
*/
private static void readDosFileAttributesIntoFileInfo(FileInfo fileInfo, Path path) {
try {
DosFileAttributes attributes = Files.readAttributes(path, DosFileAttributes.class, LinkOption.NOFOLLOW_LINKS);

fileInfo.setName(path.getFileName().toString()); // path is not a root, so filename is available
fileInfo.setExists(true);
fileInfo.setLastModified(attributes.lastModifiedTime().toMillis());
fileInfo.setLength(attributes.size());
fileInfo.setDirectory(attributes.isDirectory());
fileInfo.setAttribute(EFS.ATTRIBUTE_ARCHIVE, attributes.isArchive());
fileInfo.setAttribute(EFS.ATTRIBUTE_READ_ONLY, attributes.isReadOnly());
fileInfo.setAttribute(EFS.ATTRIBUTE_HIDDEN, attributes.isHidden());
if (attributes.isSymbolicLink()) {
setSymLink(path, fileInfo);
// For sym-links, DosFileAttributes.isDirectory() always returns false.
// Determine the real type of the link target and follow-links to the eventual target
fileInfo.setDirectory(Files.isDirectory(path));
}
} catch (NoSuchFileException _) { // file just does not exist
} catch (IOException _) {
// Leave alone and continue.
fileInfo.setError(IFileInfo.IO_ERROR);
}
}

@SuppressWarnings("static-access")
private static void convertFindDataWToFileInfo(MemorySegment mem, FileInfo info, Path file) throws IOException {
/**
Expand Down Expand Up @@ -240,12 +302,16 @@ private static void convertFindDataWToFileInfo(MemorySegment mem, FileInfo info,

boolean isReparsePoint = isSet(dwFileAttributes, FileAPI.FILE_ATTRIBUTE_REPARSE_POINT());
if (isReparsePoint && dwReserved0 == FileAPI.IO_REPARSE_TAG_SYMLINK()) {
Path linkTarget = Files.readSymbolicLink(file);
info.setAttribute(EFS.ATTRIBUTE_SYMLINK, true);
info.setStringAttribute(EFS.ATTRIBUTE_LINK_TARGET, linkTarget.toString());
setSymLink(file, info);
}
}

private static void setSymLink(Path path, FileInfo info) throws IOException {
Path linkTarget = Files.readSymbolicLink(path);
info.setAttribute(EFS.ATTRIBUTE_SYMLINK, true);
info.setStringAttribute(EFS.ATTRIBUTE_LINK_TARGET, linkTarget.toString());
}

private static final Instant WINDOWS_REFERENCE_DATE = LocalDateTime.of(1601, Month.JANUARY, 1, 0, 0).toInstant(ZoneOffset.UTC);

// https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public int getSupportedAttributes() {
}

@Override
public FileInfo fetchFileInfo(String fileName) {
public FileInfo fetchFileInfo(String fileName, int options) {
return LinuxFileNatives.fetchFileInfo(fileName);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class DefaultHandler extends NativeHandler {
| EFS.ATTRIBUTE_READ_ONLY | EFS.ATTRIBUTE_EXECUTABLE; // based on Java API

@Override
public FileInfo fetchFileInfo(String fileName) {
public FileInfo fetchFileInfo(String fileName, int options) {
Path path = Paths.get(fileName);
FileInfo info = new FileInfo();
boolean exists = Files.exists(path);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public class PosixHandler extends NativeHandler {
| EFS.ATTRIBUTE_OTHER_READ | EFS.ATTRIBUTE_OTHER_WRITE | EFS.ATTRIBUTE_OTHER_EXECUTE; // other

@Override
public FileInfo fetchFileInfo(String fileName) {
public FileInfo fetchFileInfo(String fileName, int options) {
Path path = Paths.get(fileName);
FileInfo info = new FileInfo();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public int getSupportedAttributes() {
}

@Override
public FileInfo fetchFileInfo(String fileName) {
public FileInfo fetchFileInfo(String fileName, int options) {
return UnixFileNatives.fetchFileInfo(fileName);
}

Expand Down
Loading
Loading