Copyright (c) 2005, 2016 IBM Corporation and others. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which accompanies this distribution, and is available at https://www.eclipse.org/legal/epl-2.0/ SPDX-License-Identifier: EPL-2.0 Contributors: IBM Corporation - initial API and implementation Martin Oberhuber (Wind River) - [294429] Avoid substring baggage in FileInfo Martin Lippert (VMware) - [394607] Poor performance when using findFilesForLocationURI Sergey Prigogin (Google) - [433061] Deletion of project follows symbolic links [464072] Refresh on Access ignored during text search Andrey Loskutov (loskutov@gmx.de) - [500306] Read-only files, and projects containing them, cannot be deleted
/******************************************************************************* * Copyright (c) 2005, 2016 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * IBM Corporation - initial API and implementation * Martin Oberhuber (Wind River) - [294429] Avoid substring baggage in FileInfo * Martin Lippert (VMware) - [394607] Poor performance when using findFilesForLocationURI * Sergey Prigogin (Google) - [433061] Deletion of project follows symbolic links * [464072] Refresh on Access ignored during text search * Andrey Loskutov (loskutov@gmx.de) - [500306] Read-only files, and projects containing them, cannot be deleted *******************************************************************************/
package org.eclipse.core.internal.filesystem.local; import java.io.*; import java.net.URI; import java.nio.file.*; import org.eclipse.core.filesystem.*; import org.eclipse.core.filesystem.URIUtil; import org.eclipse.core.filesystem.provider.FileInfo; import org.eclipse.core.filesystem.provider.FileStore; import org.eclipse.core.internal.filesystem.Messages; import org.eclipse.core.internal.filesystem.Policy; import org.eclipse.core.runtime.*; import org.eclipse.core.runtime.Path; import org.eclipse.osgi.util.NLS;
File system implementation based on storage of files in the local operating system's file system.
/** * File system implementation based on storage of files in the local * operating system's file system. */
public class LocalFile extends FileStore {
The java.io.File that this store represents.
/** * The java.io.File that this store represents. */
protected final File file;
The absolute file system path of the file represented by this store.
/** * The absolute file system path of the file represented by this store. */
protected final String filePath;
cached value for the toURI method
/** * cached value for the toURI method */
private URI uri; private static int attributes(File aFile) { if (!aFile.exists() || aFile.canWrite()) return EFS.NONE; return EFS.ATTRIBUTE_READ_ONLY; }
Creates a new local file.
Params:
  • file – The file this local file represents
/** * Creates a new local file. * * @param file The file this local file represents */
public LocalFile(File file) { this.file = file; this.filePath = file.getAbsolutePath(); }
This method is called after a failure to modify a file or directory. Check to see if the parent is read-only and if so then throw an exception with a more specific message and error code.
Params:
  • target – The file that we failed to modify
  • exception – The low level exception that occurred, or null
Throws:
  • CoreException – A more specific exception if the parent is read-only
/** * This method is called after a failure to modify a file or directory. * Check to see if the parent is read-only and if so then * throw an exception with a more specific message and error code. * * @param target The file that we failed to modify * @param exception The low level exception that occurred, or <code>null</code> * @throws CoreException A more specific exception if the parent is read-only */
private void checkReadOnlyParent(File target, Throwable exception) throws CoreException { File parent = target.getParentFile(); if (parent != null && (attributes(parent) & EFS.ATTRIBUTE_READ_ONLY) != 0) { String message = NLS.bind(Messages.readOnlyParent, target.getAbsolutePath()); Policy.error(EFS.ERROR_PARENT_READ_ONLY, message, exception); } }
This method is called after a failure to modify a directory. Check to see if the target is not writable (e.g. device doesn't not exist) and if so then throw an exception with a more specific message and error code.
Params:
  • target – The directory that we failed to modify
  • exception – The low level exception that occurred, or null
Throws:
  • CoreException – A more specific exception if the target is not writable
/** * This method is called after a failure to modify a directory. * Check to see if the target is not writable (e.g. device doesn't not exist) and if so then * throw an exception with a more specific message and error code. * * @param target The directory that we failed to modify * @param exception The low level exception that occurred, or <code>null</code> * @throws CoreException A more specific exception if the target is not writable */
private void checkTargetIsNotWritable(File target, Throwable exception) throws CoreException { if (!target.canWrite()) { String message = NLS.bind(Messages.couldNotWrite, target.getAbsolutePath()); Policy.error(EFS.ERROR_WRITE, message, exception); } } @Override public String[] childNames(int options, IProgressMonitor monitor) { String[] names = file.list(); return (names == null ? EMPTY_STRING_ARRAY : names); } @Override public void copy(IFileStore destFile, int options, IProgressMonitor monitor) throws CoreException { if (destFile instanceof LocalFile) { File source = file; File destination = ((LocalFile) destFile).file; //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 (source.getCanonicalFile().equals(destination.getCanonicalFile())) { //nothing to do return; } } catch (IOException e) { String message = NLS.bind(Messages.couldNotRead, source.getAbsolutePath()); Policy.error(EFS.ERROR_READ, message, e); } } //fall through to super implementation super.copy(destFile, options, monitor); } @Override public void delete(int options, IProgressMonitor monitor) throws CoreException { if (monitor == null) monitor = new NullProgressMonitor(); else monitor = new InfiniteProgress(monitor); try { monitor.beginTask(NLS.bind(Messages.deleting, this), 200); String message = Messages.deleteProblem; MultiStatus result = new MultiStatus(Policy.PI_FILE_SYSTEM, EFS.ERROR_DELETE, message, null); internalDelete(file, filePath, result, monitor); if (!result.isOK()) throw new CoreException(result); } finally { monitor.done(); } } @Override public boolean equals(Object obj) { if (!(obj instanceof LocalFile)) return false; //Mac oddity: file.equals returns false when case is different even when //file system is not case sensitive (Radar bug 3190672) LocalFile otherFile = (LocalFile) obj; if (LocalFileSystem.MACOSX) return filePath.equalsIgnoreCase(otherFile.filePath); return file.equals(otherFile.file); } @Override public IFileInfo fetchInfo(int options, IProgressMonitor monitor) { FileInfo info = LocalFileNativesManager.fetchFileInfo(filePath); //natives don't set the file name on all platforms if (info.getName().isEmpty()) { String name = file.getName(); //Bug 294429: make sure that substring baggage is removed info.setName(new String(name.toCharArray())); } return info; } @Deprecated @Override public IFileStore getChild(IPath path) { return new LocalFile(new File(file, path.toOSString())); } @Override public IFileStore getFileStore(IPath path) { return new LocalFile(new Path(file.getPath()).append(path).toFile()); } @Override public IFileStore getChild(String name) { return new LocalFile(new File(file, name)); } @Override public IFileSystem getFileSystem() { return LocalFileSystem.getInstance(); } @Override public String getName() { return file.getName(); } @Override public IFileStore getParent() { File parent = file.getParentFile(); return parent == null ? null : new LocalFile(parent); } @Override public int hashCode() { if (LocalFileSystem.MACOSX) return filePath.toLowerCase().hashCode(); return file.hashCode(); }
Deletes the given file recursively, adding failure info to the provided status object. The filePath is passed as a parameter to optimize java.io.File object creation.
/** * Deletes the given file recursively, adding failure info to * the provided status object. The filePath is passed as a parameter * to optimize java.io.File object creation. */
private boolean internalDelete(File target, String pathToDelete, MultiStatus status, IProgressMonitor monitor) { if (monitor.isCanceled()) { throw new OperationCanceledException(); } try { try { // First try to delete - this should succeed for files and symbolic links to directories. Files.deleteIfExists(target.toPath()); return true; } catch (AccessDeniedException e) { // If the file is read only, it can't be deleted via Files.deleteIfExists() // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=500306 if (target.delete()) { return true; } throw e; } } catch (DirectoryNotEmptyException e) { monitor.subTask(NLS.bind(Messages.deleting, target)); String[] list = target.list(); if (list == null) list = EMPTY_STRING_ARRAY; int parentLength = pathToDelete.length(); boolean failedRecursive = false; for (int i = 0, imax = list.length; i < imax; i++) { if (monitor.isCanceled()) { throw new OperationCanceledException(); } // Optimized creation of child path object StringBuilder childBuffer = new StringBuilder(parentLength + list[i].length() + 1); childBuffer.append(pathToDelete); childBuffer.append(File.separatorChar); childBuffer.append(list[i]); String childName = childBuffer.toString(); // Try best effort on all children so put logical OR at end. failedRecursive = !internalDelete(new java.io.File(childName), childName, status, monitor) || failedRecursive; monitor.worked(1); } try { // Don't try to delete the root if one of the children failed. if (!failedRecursive && Files.deleteIfExists(target.toPath())) return true; } catch (Exception e1) { // We caught a runtime exception so log it. String message = NLS.bind(Messages.couldnotDelete, target.getAbsolutePath()); status.add(new Status(IStatus.ERROR, Policy.PI_FILE_SYSTEM, EFS.ERROR_DELETE, message, e1)); return false; } // If we got this far, we failed. String message = null; if (fetchInfo().getAttribute(EFS.ATTRIBUTE_READ_ONLY)) { message = NLS.bind(Messages.couldnotDeleteReadOnly, target.getAbsolutePath()); } else { message = NLS.bind(Messages.couldnotDelete, target.getAbsolutePath()); } status.add(new Status(IStatus.ERROR, Policy.PI_FILE_SYSTEM, EFS.ERROR_DELETE, message, null)); return false; } catch (IOException e) { String message = NLS.bind(Messages.couldnotDelete, target.getAbsolutePath()); status.add(new Status(IStatus.ERROR, Policy.PI_FILE_SYSTEM, EFS.ERROR_DELETE, message, e)); return false; } } @Override public boolean isParentOf(IFileStore other) { if (!(other instanceof LocalFile)) return false; String thisPath = filePath; String thatPath = ((LocalFile) other).filePath; int thisLength = thisPath.length(); int thatLength = thatPath.length(); //if equal then not a parent if (thisLength >= thatLength) return false; if (getFileSystem().isCaseSensitive()) { if (thatPath.indexOf(thisPath) != 0) return false; } else { if (thatPath.toLowerCase().indexOf(thisPath.toLowerCase()) != 0) return false; } //The common portion must end with a separator character for this to be a parent of that return thisPath.charAt(thisLength - 1) == File.separatorChar || thatPath.charAt(thisLength) == File.separatorChar; } @Override public IFileStore mkdir(int options, IProgressMonitor monitor) throws CoreException { boolean shallow = (options & EFS.SHALLOW) != 0; //must be a directory try { if (shallow) { Files.createDirectory(file.toPath()); } else { Files.createDirectories(file.toPath()); } } catch (FileAlreadyExistsException e) { if (!file.isDirectory()) { String message = NLS.bind(Messages.failedCreateWrongType, filePath); Policy.error(EFS.ERROR_WRONG_TYPE, message, e); } } catch (AccessDeniedException e) { if (!file.isDirectory()) { checkReadOnlyParent(file, e); String message = NLS.bind(Messages.failedCreateAccessDenied, filePath); Policy.error(EFS.ERROR_AUTH_FAILED, message, e); } } catch (NoSuchFileException e) { if (!file.isDirectory()) { String parentPath = file.getParent(); String message = NLS.bind(Messages.fileNotFound, parentPath != null ? parentPath : filePath); Policy.error(EFS.ERROR_NOT_EXISTS, message, e); } } catch (IOException e) { if (!file.isDirectory()) { checkReadOnlyParent(file, e); checkTargetIsNotWritable(file, e); String message = NLS.bind(Messages.couldNotWrite, filePath); Policy.error(EFS.ERROR_WRITE, message, e); } } return this; } @Override public void move(IFileStore destFile, int options, IProgressMonitor monitor) throws CoreException { if (!(destFile instanceof LocalFile)) { super.move(destFile, options, monitor); return; } File source = file; File destination = ((LocalFile) destFile).file; boolean overwrite = (options & EFS.OVERWRITE) != 0; SubMonitor subMonitor = SubMonitor.convert(monitor, NLS.bind(Messages.moving, source.getAbsolutePath()), 1); try { //this flag captures case renaming on a case-insensitive OS, or moving //two equivalent files in an environment that supports symbolic links. //in these cases we NEVER want to delete anything boolean sourceEqualsDest = false; try { sourceEqualsDest = source.getCanonicalFile().equals(destination.getCanonicalFile()); } catch (IOException e) { String message = NLS.bind(Messages.couldNotMove, source.getAbsolutePath()); Policy.error(EFS.ERROR_WRITE, message, e); } if (!sourceEqualsDest && !overwrite && destination.exists()) { String message = NLS.bind(Messages.fileExists, destination.getAbsolutePath()); Policy.error(EFS.ERROR_EXISTS, message); } if (source.renameTo(destination)) { // double-check to ensure we really did move // since java.io.File#renameTo sometimes lies if (!sourceEqualsDest && source.exists()) { // XXX: document when this occurs if (destination.exists()) { // couldn't delete the source so remove the destination and throw an error // XXX: if we fail deleting the destination, the destination (root) may still exist new LocalFile(destination).delete(EFS.NONE, null); String message = NLS.bind(Messages.couldnotDelete, source.getAbsolutePath()); Policy.error(EFS.ERROR_DELETE, message); } // 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)) { // 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); } // the move was successful return; } } // for some reason renameTo didn't work if (sourceEqualsDest) { String message = NLS.bind(Messages.couldNotMove, source.getAbsolutePath()); Policy.error(EFS.ERROR_WRITE, message, null); } // fall back to default implementation super.move(destFile, options, subMonitor.newChild(1)); } finally { subMonitor.done(); } } @Override public InputStream openInputStream(int options, IProgressMonitor monitor) throws CoreException { try { return new FileInputStream(file); } catch (FileNotFoundException e) { String message; if (!file.exists()) { message = NLS.bind(Messages.fileNotFound, filePath); Policy.error(EFS.ERROR_NOT_EXISTS, message, e); } else if (file.isDirectory()) { message = NLS.bind(Messages.notAFile, filePath); Policy.error(EFS.ERROR_WRONG_TYPE, message, e); } else { message = NLS.bind(Messages.couldNotRead, filePath); Policy.error(EFS.ERROR_READ, message, e); } return null; } } @Override public OutputStream openOutputStream(int options, IProgressMonitor monitor) throws CoreException { try { return new FileOutputStream(file, (options & EFS.APPEND) != 0); } catch (FileNotFoundException e) { checkReadOnlyParent(file, e); String message; String path = filePath; if (file.isDirectory()) { message = NLS.bind(Messages.notAFile, path); Policy.error(EFS.ERROR_WRONG_TYPE, message, e); } else { message = NLS.bind(Messages.couldNotWrite, path); Policy.error(EFS.ERROR_WRITE, message, e); } return null; } } @Override public void putInfo(IFileInfo info, int options, IProgressMonitor monitor) throws CoreException { boolean success = true; if ((options & EFS.SET_ATTRIBUTES) != 0) { success &= LocalFileNativesManager.putFileInfo(filePath, info, options); } //native does not currently set last modified if ((options & EFS.SET_LAST_MODIFIED) != 0) success &= file.setLastModified(info.getLastModified()); if (!success && !file.exists()) Policy.error(EFS.ERROR_NOT_EXISTS, NLS.bind(Messages.fileNotFound, filePath)); } @Override public File toLocalFile(int options, IProgressMonitor monitor) throws CoreException { if (options == EFS.CACHE) return super.toLocalFile(options, monitor); return file; } @Override public String toString() { return file.toString(); } @Override public URI toURI() { if (this.uri == null) { this.uri = URIUtil.toURI(filePath); } return this.uri; } }