blob: 3c3cc748fe56f9643519e13c2a488d38e0dc5ac2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2016 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Martin Oberhuber (Wind River) - [210664] descriptionChanged(): ignore LF style
* Martin Oberhuber (Wind River) - [233939] findFilesForLocation() with symlinks
* James Blackburn (Broadcom Corp.) - ongoing development
* Sergey Prigogin (Google) - [338010] Resource.createLink() does not preserve symbolic links
* - [462440] IFile#getContents methods should specify the status codes for its exceptions
* Lars Vogel <Lars.Vogel@vogella.com> - Bug 473427
*******************************************************************************/
package org.eclipse.core.internal.localstore;
import java.io.*;
import java.net.URI;
import java.util.*;
import org.eclipse.core.filesystem.*;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.internal.refresh.RefreshManager;
import org.eclipse.core.internal.resources.*;
import org.eclipse.core.internal.resources.File;
import org.eclipse.core.internal.utils.*;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.Preferences.PropertyChangeEvent;
import org.eclipse.osgi.util.NLS;
import org.xml.sax.InputSource;
/**
* Manages the synchronization between the workspace's view and the file system.
*/
public class FileSystemResourceManager implements ICoreConstants, IManager, Preferences.IPropertyChangeListener {
/**
* The history store is initialized lazily - always use the accessor method
*/
protected IHistoryStore _historyStore;
protected Workspace workspace;
private volatile boolean lightweightAutoRefreshEnabled;
public FileSystemResourceManager(Workspace workspace) {
this.workspace = workspace;
}
/**
* Returns the workspace paths of all resources that may correspond to
* the given file system location. Returns an empty ArrayList if there are no
* such paths. This method does not consider whether resources actually
* exist at the given locations.
* <p>
* The workspace paths of {@link IResource#HIDDEN} project and resources
* located in {@link IResource#HIDDEN} projects won't be added to the result.
* </p>
*
*/
protected ArrayList<IPath> allPathsForLocation(URI inputLocation) {
URI canonicalLocation = FileUtil.canonicalURI(inputLocation);
// First, try the canonical version of the inputLocation.
// If the inputLocation is different from the canonical version, it will be tried second
ArrayList<IPath> results = allPathsForLocationNonCanonical(canonicalLocation);
if (results.size() == 0 && canonicalLocation != inputLocation) {
results = allPathsForLocationNonCanonical(inputLocation);
}
return results;
}
private ArrayList<IPath> allPathsForLocationNonCanonical(URI inputLocation) {
URI location = inputLocation;
final boolean isFileLocation = EFS.SCHEME_FILE.equals(inputLocation.getScheme());
final IWorkspaceRoot root = getWorkspace().getRoot();
final ArrayList<IPath> results = new ArrayList<>();
if (URIUtil.equals(location, locationURIFor(root, true))) {
//there can only be one resource at the workspace root's location
results.add(Path.ROOT);
return results;
}
IProject[] projects = root.getProjects(IContainer.INCLUDE_HIDDEN);
for (int i = 0; i < projects.length; i++) {
IProject project = projects[i];
if (!project.exists())
continue;
//check the project location
URI testLocation = locationURIFor(project, true);
if (testLocation == null)
continue;
boolean usingAnotherScheme = !inputLocation.getScheme().equals(testLocation.getScheme());
// if we are looking for file: locations try to get a file: location for this project
if (isFileLocation && !EFS.SCHEME_FILE.equals(testLocation.getScheme()))
testLocation = getFileURI(testLocation);
if (testLocation == null)
continue;
URI relative = testLocation.relativize(location);
if (!relative.isAbsolute() && !relative.equals(testLocation)) {
IPath suffix = new Path(relative.getPath());
results.add(project.getFullPath().append(suffix));
}
if (usingAnotherScheme) {
// if a different scheme is used, we can't use the AliasManager, since the manager
// map is stored using the EFS scheme, and not necessarily the SCHEME_FILE
ProjectDescription description = ((Project) project).internalGetDescription();
if (description == null)
continue;
HashMap<IPath, LinkDescription> links = description.getLinks();
if (links == null)
continue;
for (LinkDescription link : links.values()) {
IResource resource = project.findMember(link.getProjectRelativePath());
IPathVariableManager pathMan = resource == null ? project.getPathVariableManager() : resource.getPathVariableManager();
testLocation = pathMan.resolveURI(link.getLocationURI());
// if we are looking for file: locations try to get a file: location for this link
if (isFileLocation && !EFS.SCHEME_FILE.equals(testLocation.getScheme()))
testLocation = getFileURI(testLocation);
if (testLocation == null)
continue;
relative = testLocation.relativize(location);
if (!relative.isAbsolute() && !relative.equals(testLocation)) {
IPath suffix = new Path(relative.getPath());
results.add(project.getFullPath().append(link.getProjectRelativePath()).append(suffix));
}
}
}
}
try {
findLinkedResourcesPaths(inputLocation, results);
} catch (CoreException e) {
Policy.log(e);
}
return results;
}
/**
* Asynchronously auto-refresh the requested resource if {@link ResourcesPlugin#PREF_LIGHTWEIGHT_AUTO_REFRESH} is enabled.
* @param target
*/
private void asyncRefresh(IResource target) {
if (lightweightAutoRefreshEnabled) {
RefreshManager refreshManager = workspace.getRefreshManager();
// refreshManager can be null during shutdown
if (refreshManager != null) {
refreshManager.refresh(target);
}
}
}
private void findLinkedResourcesPaths(URI inputLocation, final ArrayList<IPath> results) throws CoreException {
IPath suffix = null;
IFileStore fileStore = EFS.getStore(inputLocation);
while (fileStore != null) {
IResource[] resources = workspace.getAliasManager().findResources(fileStore);
for (int i = 0; i < resources.length; i++) {
if (resources[i].isLinked()) {
IPath path = resources[i].getFullPath();
if (suffix != null)
path = path.append(suffix);
if (!results.contains(path))
results.add(path);
}
}
if (suffix == null)
suffix = Path.fromPortableString(fileStore.getName());
else
suffix = Path.fromPortableString(fileStore.getName()).append(suffix);
fileStore = fileStore.getParent();
}
}
/**
* Tries to obtain a file URI for the given URI. Returns <code>null</code> if the file system associated
* to the URI scheme does not map to the local file system.
* @param locationURI the URI to convert
* @return a file URI or <code>null</code>
*/
private URI getFileURI(URI locationURI) {
try {
IFileStore testLocationStore = EFS.getStore(locationURI);
java.io.File storeAsFile = testLocationStore.toLocalFile(EFS.NONE, null);
if (storeAsFile != null)
return URIUtil.toURI(storeAsFile.getAbsolutePath());
} catch (CoreException e) {
// we don't know such file system or some other failure, just return null
}
return null;
}
/**
* Returns all resources that correspond to the given file system location,
* including resources under linked resources. Returns an empty array if
* there are no corresponding resources.
* <p>
* If the {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is specified
* in the member flags, team private members will be included along with the
* others. If the {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} flag is
* not specified (recommended), the result will omit any team private member
* resources.
* </p>
* <p>
* If the {@link IContainer#INCLUDE_HIDDEN} flag is specified in the member
* flags, hidden members will be included along with the others. If the
* {@link IContainer#INCLUDE_HIDDEN} flag is not specified (recommended),
* the result will omit any hidden member resources.
* </p>
* <p>
* The result will also omit resources that are explicitly excluded
* from the workspace according to existing resource filters.
* </p>
*
* @param location
* the file system location
* @param files
* resources that may exist below the project level can be either
* files or folders. If this parameter is true, files will be
* returned, otherwise containers will be returned.
* @param memberFlags
* bit-wise or of member flag constants (
* {@link IContainer#INCLUDE_TEAM_PRIVATE_MEMBERS} and
* {@link IContainer#INCLUDE_HIDDEN}) indicating which members are of
* interest
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public IResource[] allResourcesFor(URI location, boolean files, int memberFlags) {
ArrayList result = allPathsForLocation(location);
int count = 0;
for (int i = 0, imax = result.size(); i < imax; i++) {
//replace the path in the list with the appropriate resource type
IResource resource = resourceFor((IPath) result.get(i), files);
if (resource == null || ((Resource) resource).isFiltered() || (((memberFlags & IContainer.INCLUDE_HIDDEN) == 0) && resource.isHidden(IResource.CHECK_ANCESTORS)) || (((memberFlags & IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS) == 0) && resource.isTeamPrivateMember(IResource.CHECK_ANCESTORS)))
resource = null;
result.set(i, resource);
//count actual resources - some paths won't have a corresponding resource
if (resource != null)
count++;
}
//convert to array and remove null elements
IResource[] toReturn = files ? (IResource[]) new IFile[count] : (IResource[]) new IContainer[count];
count = 0;
for (Iterator it = result.iterator(); it.hasNext();) {
IResource resource = (IResource) it.next();
if (resource != null)
toReturn[count++] = resource;
}
return toReturn;
}
/* (non-javadoc)
* @see IResource.getResourceAttributes
*/
public ResourceAttributes attributes(IResource resource) {
IFileStore store = getStore(resource);
IFileInfo fileInfo = store.fetchInfo();
if (!fileInfo.exists())
return null;
return FileUtil.fileInfoToAttributes(fileInfo);
}
/**
* Returns a container for the given file system location or null if there
* is no mapping for this path. If the path has only one segment, then an
* <code>IProject</code> is returned. Otherwise, the returned object
* is a <code>IFolder</code>. This method does NOT check the existence
* of a folder in the given location. Location cannot be null.
* <p>
* The result will also omit resources that are explicitly excluded
* from the workspace according to existing resource filters. If all resources
* are omitted, the result may be null.
* </p>
* <p>
* Returns a folder whose path has a minimal number of segments.
* I.e. a folder in a nested project is preferred over a folder in an enclosing project.
* </p>
*/
public IContainer containerForLocation(IPath location) {
return (IContainer) resourceForLocation(location, false);
}
/**
* Returns a resource corresponding to the given location. The
* "files" parameter is used for paths of two or more segments. If true,
* a file is returned, otherwise a folder is returned. Returns null if files is true
* and the path is not of sufficient length. Also returns null if the resource is
* filtered out by resource filters.
* <p>
* Returns a resource whose path has a minimal number of segments.
* I.e. a resource in a nested project is preferred over a resource in an enclosing project.
* </p>
*/
private IResource resourceForLocation(IPath location, boolean files) {
if (workspace.getRoot().getLocation().equals(location)) {
if (!files)
return resourceFor(Path.ROOT, false);
return null;
}
int resultProjectPathSegments = 0;
IResource result = null;
IProject[] projects = getWorkspace().getRoot().getProjects(IContainer.INCLUDE_HIDDEN);
for (int i = 0; i < projects.length; i++) {
IProject project = projects[i];
IPath projectLocation = project.getLocation();
if (projectLocation != null && projectLocation.isPrefixOf(location)) {
int segmentsToRemove = projectLocation.segmentCount();
if (segmentsToRemove > resultProjectPathSegments) {
IPath path = project.getFullPath().append(location.removeFirstSegments(segmentsToRemove));
IResource resource = resourceFor(path, files);
if (resource != null && !((Resource) resource).isFiltered()) {
resultProjectPathSegments = segmentsToRemove;
result = resource;
}
}
}
}
return result;
}
public void copy(IResource target, IResource destination, int updateFlags, IProgressMonitor monitor) throws CoreException {
String title = NLS.bind(Messages.localstore_copying, target.getFullPath());
SubMonitor subMonitor = SubMonitor.convert(monitor, title, 100);
IFileStore destinationStore = getStore(destination);
if (destinationStore.fetchInfo().exists()) {
String message = NLS.bind(Messages.localstore_resourceExists, destination.getFullPath());
throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, destination.getFullPath(), message, null);
}
getHistoryStore().copyHistory(target, destination, false);
CopyVisitor visitor = new CopyVisitor(target, destination, updateFlags, subMonitor.split(100));
UnifiedTree tree = new UnifiedTree(target);
tree.accept(visitor, IResource.DEPTH_INFINITE);
IStatus status = visitor.getStatus();
if (!status.isOK()) {
throw new ResourceException(status);
}
}
public void delete(IResource target, int flags, IProgressMonitor monitor) throws CoreException {
Resource resource = (Resource) target;
final int deleteWork = resource.countResources(IResource.DEPTH_INFINITE, false) * 2;
boolean force = (flags & IResource.FORCE) != 0;
int refreshWork = 0;
if (!force) {
refreshWork = Math.min(deleteWork, 100);
}
String title = NLS.bind(Messages.localstore_deleting, resource.getFullPath());
SubMonitor subMonitor = SubMonitor.convert(monitor, title, deleteWork + refreshWork);
MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_DELETE_LOCAL, Messages.localstore_deleteProblem, null);
List<Resource> skipList = null;
UnifiedTree tree = new UnifiedTree(target);
if (!force) {
CollectSyncStatusVisitor refreshVisitor = new CollectSyncStatusVisitor(Messages.localstore_deleteProblem, subMonitor.split(refreshWork));
refreshVisitor.setIgnoreLocalDeletions(true);
tree.accept(refreshVisitor, IResource.DEPTH_INFINITE);
status.merge(refreshVisitor.getSyncStatus());
skipList = refreshVisitor.getAffectedResources();
}
DeleteVisitor deleteVisitor = new DeleteVisitor(skipList, flags, subMonitor.split(deleteWork), deleteWork);
tree.accept(deleteVisitor, IResource.DEPTH_INFINITE);
status.merge(deleteVisitor.getStatus());
if (!status.isOK()) {
throw new ResourceException(status);
}
}
/**
* Returns true if the description on disk is different from the given byte array,
* and false otherwise.
* Since org.eclipse.core.resources 3.4.1 differences in line endings (CR, LF, CRLF)
* are not considered.
*/
private boolean descriptionChanged(IFile descriptionFile, byte[] newContents) {
InputStream oldStream = null;
try {
//buffer size: twice the description length, but maximum 8KB
int bufsize = newContents.length > 4096 ? 8192 : newContents.length * 2;
oldStream = new BufferedInputStream(descriptionFile.getContents(true), bufsize);
InputStream newStream = new ByteArrayInputStream(newContents);
//compare streams char by char, ignoring line endings
int newChar = newStream.read();
int oldChar = oldStream.read();
while (newChar >= 0 && oldChar >= 0) {
if (newChar == oldChar) {
//streams are the same
newChar = newStream.read();
oldChar = oldStream.read();
} else if ((newChar == '\r' || newChar == '\n') && (oldChar == '\r' || oldChar == '\n')) {
//got a difference, but both sides are newlines: read over newlines
while (newChar == '\r' || newChar == '\n')
newChar = newStream.read();
while (oldChar == '\r' || oldChar == '\n')
oldChar = oldStream.read();
} else {
//streams are different
return true;
}
}
//test for excess data in one stream
if (newChar >= 0 || oldChar >= 0)
return true;
return false;
} catch (Exception e) {
Policy.log(e);
//if we failed to compare, just write the new contents
} finally {
FileUtil.safeClose(oldStream);
}
return true;
}
/**
* @deprecated
*/
@Deprecated
public int doGetEncoding(IFileStore store) throws CoreException {
InputStream input = null;
try {
input = store.openInputStream(EFS.NONE, null);
int first = input.read();
int second = input.read();
if (first == -1 || second == -1)
return IFile.ENCODING_UNKNOWN;
first &= 0xFF;//converts unsigned byte to int
second &= 0xFF;
//look for the UTF-16 Byte Order Mark (BOM)
if (first == 0xFE && second == 0xFF)
return IFile.ENCODING_UTF_16BE;
if (first == 0xFF && second == 0xFE)
return IFile.ENCODING_UTF_16LE;
int third = (input.read() & 0xFF);
if (third == -1)
return IFile.ENCODING_UNKNOWN;
//look for the UTF-8 BOM
if (first == 0xEF && second == 0xBB && third == 0xBF)
return IFile.ENCODING_UTF_8;
return IFile.ENCODING_UNKNOWN;
} catch (IOException e) {
String message = NLS.bind(Messages.localstore_couldNotRead, store.toString());
throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, null, message, e);
} finally {
FileUtil.safeClose(input);
}
}
/**
* Optimized sync check for files. Returns true if the file exists and is in sync, and false
* otherwise. The intent is to let the default implementation handle the complex
* cases like gender change, case variants, etc.
*/
public boolean fastIsSynchronized(File target) {
ResourceInfo info = target.getResourceInfo(false, false);
if (target.exists(target.getFlags(info), true)) {
IFileInfo fileInfo = getStore(target).fetchInfo();
if (!fileInfo.isDirectory() && info.getLocalSyncInfo() == fileInfo.getLastModified())
return true;
}
return false;
}
public boolean fastIsSynchronized(Folder target) {
ResourceInfo info = target.getResourceInfo(false, false);
if (target.exists(target.getFlags(info), true)) {
IFileInfo fileInfo = getStore(target).fetchInfo();
if (!fileInfo.exists() && info.getLocalSyncInfo() == fileInfo.getLastModified())
return true;
}
return false;
}
/**
* Returns an IFile for the given file system location or null if there
* is no mapping for this path. This method does NOT check the existence
* of a file in the given location. Location cannot be null.
* <p>
* The result will also omit resources that are explicitly excluded
* from the workspace according to existing resource filters. If all resources
* are omitted, the result may be null.
* </p>
* <p>
* Returns a file whose path has a minimal number of segments.
* I.e. a file in a nested project is preferred over a file in an enclosing project.
* </p>
*/
public IFile fileForLocation(IPath location) {
return (IFile) resourceForLocation(location, true);
}
/**
* @deprecated
*/
@Deprecated
public int getEncoding(File target) throws CoreException {
// thread safety: (the location can be null if the project for this file does not exist)
IFileStore store = getStore(target);
if (!store.fetchInfo().exists()) {
String message = NLS.bind(Messages.localstore_fileNotFound, store.toString());
throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, target.getFullPath(), message, null);
}
return doGetEncoding(store);
}
public IHistoryStore getHistoryStore() {
if (_historyStore == null) {
IPath location = getWorkspace().getMetaArea().getHistoryStoreLocation();
location.toFile().mkdirs();
IFileStore store = EFS.getLocalFileSystem().getStore(location);
_historyStore = new HistoryStore2(getWorkspace(), store, 256);
}
return _historyStore;
}
/**
* Returns the real name of the resource on disk. Returns null if no local
* file exists by that name. This is useful when dealing with
* case insensitive file systems.
*/
public String getLocalName(IFileStore target) {
return target.fetchInfo().getName();
}
protected IPath getProjectDefaultLocation(IProject project) {
return workspace.getRoot().getLocation().append(project.getFullPath());
}
/**
* Never returns null
* @param target
* @return The file store for this resource
*/
public IFileStore getStore(IResource target) {
try {
return getStoreRoot(target).createStore(target.getFullPath(), target);
} catch (CoreException e) {
//callers aren't expecting failure here, so return null file system
return EFS.getNullFileSystem().getStore(target.getFullPath());
}
}
/**
* Returns the file store root for the provided resource. Never returns null.
*/
private FileStoreRoot getStoreRoot(IResource target) {
ResourceInfo info = workspace.getResourceInfo(target.getFullPath(), true, false);
FileStoreRoot root;
if (info != null) {
root = info.getFileStoreRoot();
if (root != null && root.isValid())
return root;
if (info.isSet(ICoreConstants.M_VIRTUAL)) {
ProjectDescription description = ((Project) target.getProject()).internalGetDescription();
if (description != null) {
setLocation(target, info, description.getGroupLocationURI(target.getProjectRelativePath()));
return info.getFileStoreRoot();
}
return info.getFileStoreRoot();
}
if (info.isSet(ICoreConstants.M_LINK)) {
ProjectDescription description = ((Project) target.getProject()).internalGetDescription();
if (description != null) {
final URI linkLocation = description.getLinkLocationURI(target.getProjectRelativePath());
//if we can't determine the link location, fall through to parent resource
if (linkLocation != null) {
setLocation(target, info, linkLocation);
return info.getFileStoreRoot();
}
}
}
}
final IContainer parent = target.getParent();
if (parent == null) {
//this is the root, so we know where this must be located
//initialize root location
info = workspace.getResourceInfo(Path.ROOT, false, true);
final IWorkspaceRoot rootResource = workspace.getRoot();
setLocation(rootResource, info, URIUtil.toURI(rootResource.getLocation()));
return info.getFileStoreRoot();
}
root = getStoreRoot(parent);
if (info != null)
info.setFileStoreRoot(root);
return root;
}
protected Workspace getWorkspace() {
return workspace;
}
/**
* Returns whether the project has any local content on disk.
*/
public boolean hasSavedContent(IProject project) {
return getStore(project).fetchInfo().exists();
}
/**
* Returns whether the project has a project description file on disk.
*/
public boolean hasSavedDescription(IProject project) {
return getStore(project).getChild(IProjectDescription.DESCRIPTION_FILE_NAME).fetchInfo().exists();
}
/**
* Initializes the file store for a resource.
*
* @param target The resource to initialize the file store for.
* @param location the File system location of this resource on disk
* @return The file store for the provided resource
*/
private IFileStore initializeStore(IResource target, URI location) throws CoreException {
ResourceInfo info = ((Resource) target).getResourceInfo(false, true);
setLocation(target, info, location);
FileStoreRoot root = getStoreRoot(target);
return root.createStore(target.getFullPath(), target);
}
/**
* The target must exist in the workspace. This method must only ever
* be called from Project.writeDescription(), because that method ensures
* that the description isn't then immediately discovered as a new change.
* @return true if a new description was written, and false if it wasn't written
* because it was unchanged
*/
public boolean internalWrite(IProject target, IProjectDescription description, int updateFlags, boolean hasPublicChanges, boolean hasPrivateChanges) throws CoreException {
//write the project's private description to the metadata area
if (hasPrivateChanges)
getWorkspace().getMetaArea().writePrivateDescription(target);
if (!hasPublicChanges)
return false;
//can't do anything if there's no description
if (description == null)
return false;
//write the model to a byte array
ByteArrayOutputStream out = new ByteArrayOutputStream();
IFile descriptionFile = target.getFile(IProjectDescription.DESCRIPTION_FILE_NAME);
try {
new ModelObjectWriter().write(description, out, FileUtil.getLineSeparator(descriptionFile));
} catch (IOException e) {
String msg = NLS.bind(Messages.resources_writeMeta, target.getFullPath());
throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, target.getFullPath(), msg, e);
}
byte[] newContents = out.toByteArray();
//write the contents to the IFile that represents the description
if (!descriptionFile.exists())
workspace.createResource(descriptionFile, false);
else {
//if the description has not changed, don't write anything
if (!descriptionChanged(descriptionFile, newContents))
return false;
}
ByteArrayInputStream in = new ByteArrayInputStream(newContents);
IFileStore descriptionFileStore = ((Resource) descriptionFile).getStore();
IFileInfo fileInfo = descriptionFileStore.fetchInfo();
if (fileInfo.getAttribute(EFS.ATTRIBUTE_READ_ONLY)) {
IStatus result = getWorkspace().validateEdit(new IFile[] {descriptionFile}, null);
if (!result.isOK())
throw new ResourceException(result);
// re-read the file info in case the file attributes were modified
fileInfo = descriptionFileStore.fetchInfo();
}
//write the project description file (don't use API because scheduling rule might not match)
write(descriptionFile, in, fileInfo, IResource.FORCE, false, SubMonitor.convert(null));
workspace.getAliasManager().updateAliases(descriptionFile, getStore(descriptionFile), IResource.DEPTH_ZERO, SubMonitor.convert(null));
//update the timestamp on the project as well so we know when it has
//been changed from the outside
long lastModified = ((Resource) descriptionFile).getResourceInfo(false, false).getLocalSyncInfo();
ResourceInfo info = ((Resource) target).getResourceInfo(false, true);
updateLocalSync(info, lastModified);
//for backwards compatibility, ensure the old .prj file is deleted
getWorkspace().getMetaArea().clearOldDescription(target);
return true;
}
/**
* Returns true if the given project's description is synchronized with
* the project description file on disk, and false otherwise.
*/
public boolean isDescriptionSynchronized(IProject target) {
//sync info is stored on the description file, and on project info.
//when the file is changed by someone else, the project info modification
//stamp will be out of date
IFile descriptionFile = target.getFile(IProjectDescription.DESCRIPTION_FILE_NAME);
ResourceInfo projectInfo = ((Resource) target).getResourceInfo(false, false);
if (projectInfo == null)
return false;
return projectInfo.getLocalSyncInfo() == getStore(descriptionFile).fetchInfo().getLastModified();
}
/**
* Returns true if the given resource is synchronized with the file system
* to the given depth. Returns false otherwise.
*
* Any discovered out-of-sync resources are scheduled to be brought
* back in sync, if {@link ResourcesPlugin#PREF_LIGHTWEIGHT_AUTO_REFRESH} is
* enabled.
*
* @see IResource#isSynchronized(int)
*/
public boolean isSynchronized(IResource target, int depth) {
switch (target.getType()) {
case IResource.ROOT :
if (depth == IResource.DEPTH_ZERO)
return true;
//check sync on child projects.
depth = depth == IResource.DEPTH_ONE ? IResource.DEPTH_ZERO : depth;
IProject[] projects = ((IWorkspaceRoot) target).getProjects(IContainer.INCLUDE_HIDDEN);
for (int i = 0; i < projects.length; i++) {
if (!isSynchronized(projects[i], depth))
return false;
}
return true;
case IResource.PROJECT :
if (!target.isAccessible())
return true;
break;
case IResource.FOLDER :
if (fastIsSynchronized((Folder) target))
return true;
break;
case IResource.FILE :
if (fastIsSynchronized((File) target))
return true;
break;
}
IsSynchronizedVisitor visitor = new IsSynchronizedVisitor(SubMonitor.convert(null));
UnifiedTree tree = new UnifiedTree(target);
try {
tree.accept(visitor, depth);
} catch (CoreException e) {
Policy.log(e);
return false;
} catch (IsSynchronizedVisitor.ResourceChangedException e) {
// Ask refresh manager to bring out-of-sync resource back into sync when convenient
asyncRefresh(e.target);
//visitor throws an exception if out of sync
return false;
}
return true;
}
/**
* Check whether the preference {@link ResourcesPlugin#PREF_LIGHTWEIGHT_AUTO_REFRESH} is
* enabled. When this preference is true the Resources plugin automatically refreshes
* resources which are known to be out-of-sync, and may install lightweight filesystem
* notification hooks.
* @return whether this FSRM is automatically refreshing discovered out-of-sync resources
*/
public boolean isLightweightAutoRefreshEnabled() {
return lightweightAutoRefreshEnabled;
}
public void link(Resource target, URI location, IFileInfo fileInfo) throws CoreException {
initializeStore(target, location);
ResourceInfo info = target.getResourceInfo(false, true);
long lastModified = fileInfo == null ? 0 : fileInfo.getLastModified();
if (lastModified == 0)
info.clearModificationStamp();
updateLocalSync(info, lastModified);
}
/**
* Returns the resolved, absolute file system location of the given resource.
* Returns null if the location could not be resolved. No canonicalization is
* applied to the returned path.
*
* @param target the resource to get the location for
*/
public IPath locationFor(IResource target) {
return locationFor(target, false);
}
/**
* Returns the resolved, absolute file system location of the given resource.
* Returns null if the location could not be resolved.
*
* @param target the resource to get the location for
* @param canonical if {@code true}, the prefix of the returned path corresponding
* to the resource's file store root will be canonicalized
*/
public IPath locationFor(IResource target, boolean canonical) {
return getStoreRoot(target).localLocation(target.getFullPath(), target, false);
}
/**
* Returns the resolved, absolute file system location of the given resource.
* Returns null if the location could not be resolved. No canonicalization is
* applied to the returned URI.
*
* @param target the resource to get the location URI for
*/
public URI locationURIFor(IResource target) {
return locationURIFor(target, false);
}
/**
* Returns the resolved, absolute file system location of the given resource.
* Returns null if the location could not be resolved.
*
* @param target the resource to get the location URI for
* @param canonical if {@code true}, the prefix of the path of the returned URI
* corresponding to resource's file store root will be canonicalized
*/
public URI locationURIFor(IResource target, boolean canonical) {
return getStoreRoot(target).computeURI(target.getFullPath(), canonical);
}
public void move(IResource source, IFileStore destination, int flags, IProgressMonitor monitor) throws CoreException {
//TODO figure out correct semantics for case where destination exists on disk
getStore(source).move(destination, EFS.NONE, monitor);
}
@Deprecated
@Override
public void propertyChange(PropertyChangeEvent event) {
if (ResourcesPlugin.PREF_LIGHTWEIGHT_AUTO_REFRESH.equals(event.getProperty()))
lightweightAutoRefreshEnabled = Boolean.valueOf(event.getNewValue().toString());
}
public InputStream read(IFile target, boolean force, IProgressMonitor monitor) throws CoreException {
IFileStore store = getStore(target);
if (lightweightAutoRefreshEnabled || !force) {
final IFileInfo fileInfo = store.fetchInfo();
if (!fileInfo.exists()) {
asyncRefresh(target);
String message = NLS.bind(Messages.localstore_fileNotFound, store.toString());
throw new ResourceException(IResourceStatus.RESOURCE_NOT_FOUND, target.getFullPath(), message, null);
}
ResourceInfo info = ((Resource) target).getResourceInfo(true, false);
int flags = ((Resource) target).getFlags(info);
((Resource) target).checkExists(flags, true);
if (fileInfo.getLastModified() != info.getLocalSyncInfo()) {
asyncRefresh(target);
if (!force) {
String message = NLS.bind(Messages.localstore_resourceIsOutOfSync, target.getFullPath());
throw new ResourceException(IResourceStatus.OUT_OF_SYNC_LOCAL, target.getFullPath(), message, null);
}
}
}
return store.openInputStream(EFS.NONE, monitor);
}
/**
* Reads and returns the project description for the given project.
* Never returns null.
* @param target the project whose description should be read.
* @param creation true if this project is just being created, in which
* case the private project information (including the location) needs to be read
* from disk as well.
* @exception CoreException if there was any failure to read the project
* description, or if the description was missing.
*/
public ProjectDescription read(IProject target, boolean creation) throws CoreException {
//read the project location if this project is being created
URI projectLocation = null;
ProjectDescription privateDescription = null;
if (creation) {
privateDescription = new ProjectDescription();
getWorkspace().getMetaArea().readPrivateDescription(target, privateDescription);
projectLocation = privateDescription.getLocationURI();
} else {
IProjectDescription description = ((Project) target).internalGetDescription();
if (description != null && description.getLocationURI() != null) {
projectLocation = description.getLocationURI();
}
}
final boolean isDefaultLocation = projectLocation == null;
if (isDefaultLocation) {
projectLocation = URIUtil.toURI(getProjectDefaultLocation(target));
}
IFileStore projectStore = initializeStore(target, projectLocation);
IFileStore descriptionStore = projectStore.getChild(IProjectDescription.DESCRIPTION_FILE_NAME);
ProjectDescription description = null;
//hold onto any exceptions until after sync info is updated, then throw it
ResourceException error = null;
InputStream in = null;
try {
in = new BufferedInputStream(descriptionStore.openInputStream(EFS.NONE, SubMonitor.convert(null)));
// IFileStore#openInputStream may cancel the monitor, thus the monitor state is checked
description = new ProjectDescriptionReader(target).read(new InputSource(in));
} catch (OperationCanceledException e) {
String msg = NLS.bind(Messages.resources_missingProjectMeta, target.getName());
throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, target.getFullPath(), msg, e);
} catch (CoreException e) {
//try the legacy location in the meta area
description = getWorkspace().getMetaArea().readOldDescription(target);
if (description != null)
return description;
if (!descriptionStore.fetchInfo().exists()) {
String msg = NLS.bind(Messages.resources_missingProjectMeta, target.getName());
throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, target.getFullPath(), msg, null);
}
String msg = NLS.bind(Messages.resources_readProjectMeta, target.getName());
error = new ResourceException(IResourceStatus.FAILED_READ_METADATA, target.getFullPath(), msg, e);
} finally {
FileUtil.safeClose(in);
}
if (error == null && description == null) {
String msg = NLS.bind(Messages.resources_readProjectMeta, target.getName());
error = new ResourceException(IResourceStatus.FAILED_READ_METADATA, target.getFullPath(), msg, null);
}
if (description != null) {
if (!isDefaultLocation)
description.setLocationURI(projectLocation);
if (creation && privateDescription != null)
// Bring dynamic state back to life
description.updateDynamicState(privateDescription);
}
long lastModified = descriptionStore.fetchInfo().getLastModified();
IFile descriptionFile = target.getFile(IProjectDescription.DESCRIPTION_FILE_NAME);
//don't get a mutable copy because we might be in restore which isn't an operation
//it doesn't matter anyway because local sync info is not included in deltas
ResourceInfo info = ((Resource) descriptionFile).getResourceInfo(false, false);
if (info == null) {
//create a new resource on the sly -- don't want to start an operation
info = getWorkspace().createResource(descriptionFile, false);
updateLocalSync(info, lastModified);
}
//if the project description has changed between sessions, let it remain
//out of sync -- that way link changes will be reconciled on next refresh
if (!creation)
updateLocalSync(info, lastModified);
//update the timestamp on the project as well so we know when it has
//been changed from the outside
info = ((Resource) target).getResourceInfo(false, true);
updateLocalSync(info, lastModified);
if (error != null)
throw error;
return description;
}
public boolean refresh(IResource target, int depth, boolean updateAliases, IProgressMonitor monitor) throws CoreException {
switch (target.getType()) {
case IResource.ROOT :
return refreshRoot((IWorkspaceRoot) target, depth, updateAliases, monitor);
case IResource.PROJECT :
if (!target.isAccessible())
return false;
//fall through
case IResource.FOLDER :
case IResource.FILE :
return refreshResource(target, depth, updateAliases, monitor);
}
return false;
}
protected boolean refreshResource(IResource target, int depth, boolean updateAliases, IProgressMonitor monitor) throws CoreException {
String title = NLS.bind(Messages.localstore_refreshing, target.getFullPath());
SubMonitor subMonitor = SubMonitor.convert(monitor, title, 100);
IFileTree fileTree = null;
// If there can be more than one resource to refresh, try to get the whole tree in one shot, if the file system supports it.
if (depth != IResource.DEPTH_ZERO) {
IFileStore fileStore = ((Resource) target).getStore();
fileTree = fileStore.getFileSystem().fetchFileTree(fileStore, subMonitor.newChild(2));
}
UnifiedTree tree = fileTree == null ? new UnifiedTree(target) : new UnifiedTree(target, fileTree);
SubMonitor refreshMonitor = subMonitor.newChild(98);
RefreshLocalVisitor visitor = updateAliases ? new RefreshLocalAliasVisitor(refreshMonitor) : new RefreshLocalVisitor(refreshMonitor);
tree.accept(visitor, depth);
IStatus result = visitor.getErrorStatus();
if (!result.isOK())
throw new ResourceException(result);
return visitor.resourcesChanged();
}
/**
* Synchronizes the entire workspace with the local file system.
* The current implementation does this by synchronizing each of the
* projects currently in the workspace. A better implementation may
* be possible.
*/
protected boolean refreshRoot(IWorkspaceRoot target, int depth, boolean updateAliases, IProgressMonitor monitor) throws CoreException {
IProject[] projects = target.getProjects(IContainer.INCLUDE_HIDDEN);
String title = Messages.localstore_refreshingRoot;
SubMonitor subMonitor = SubMonitor.convert(monitor, title, projects.length);
// if doing depth zero, there is nothing to do (can't refresh the root).
// Note that we still need to do the beginTask, done pair.
if (depth == IResource.DEPTH_ZERO)
return false;
boolean changed = false;
// drop the depth by one level since processing the root counts as one level.
depth = depth == IResource.DEPTH_ONE ? IResource.DEPTH_ZERO : depth;
for (int i = 0; i < projects.length; i++) {
changed |= refresh(projects[i], depth, updateAliases, subMonitor.newChild(1));
}
return changed;
}
/**
* Returns the resource corresponding to the given workspace path. The
* "files" parameter is used for paths of two or more segments. If true,
* a file is returned, otherwise a folder is returned. Returns null if files is true
* and the path is not of sufficient length.
*/
protected IResource resourceFor(IPath path, boolean files) {
int numSegments = path.segmentCount();
if (files && numSegments < ICoreConstants.MINIMUM_FILE_SEGMENT_LENGTH)
return null;
IWorkspaceRoot root = getWorkspace().getRoot();
if (path.isRoot())
return root;
if (numSegments == 1)
return root.getProject(path.segment(0));
return files ? (IResource) root.getFile(path) : (IResource) root.getFolder(path);
}
/* (non-javadoc)
* @see IResouce.setLocalTimeStamp
*/
public long setLocalTimeStamp(IResource target, ResourceInfo info, long value) throws CoreException {
IFileStore store = getStore(target);
IFileInfo fileInfo = store.fetchInfo();
fileInfo.setLastModified(value);
store.putInfo(fileInfo, EFS.SET_LAST_MODIFIED, null);
//actual value may be different depending on file system granularity
fileInfo = store.fetchInfo();
long actualValue = fileInfo.getLastModified();
updateLocalSync(info, actualValue);
return actualValue;
}
/**
* The storage location for a resource has changed; update the location.
* @param target
* @param info
* @param location
*/
public void setLocation(IResource target, ResourceInfo info, URI location) {
FileStoreRoot oldRoot = info.getFileStoreRoot();
if (location != null) {
location = FileUtil.realURI(location); // Normalize case as it exists on the file system.
info.setFileStoreRoot(new FileStoreRoot(location, target.getFullPath()));
} else {
//project is in default location so clear the store root
info.setFileStoreRoot(null);
}
if (oldRoot != null)
oldRoot.setValid(false);
}
/* (non-javadoc)
* @see IResource.setResourceAttributes
*/
public void setResourceAttributes(IResource resource, ResourceAttributes attributes) throws CoreException {
IFileStore store = getStore(resource);
//when the executable bit is changed on a folder a refresh is required
boolean refresh = false;
if (resource instanceof IContainer && ((store.getFileSystem().attributes() & EFS.ATTRIBUTE_EXECUTABLE) != 0))
refresh = store.fetchInfo().getAttribute(EFS.ATTRIBUTE_EXECUTABLE) != attributes.isExecutable();
store.putInfo(FileUtil.attributesToFileInfo(attributes), EFS.SET_ATTRIBUTES, null);
//must refresh in the background because we are not inside an operation
if (refresh)
workspace.getRefreshManager().refresh(resource);
}
@Override
public void shutdown(IProgressMonitor monitor) throws CoreException {
if (_historyStore != null)
_historyStore.shutdown(monitor);
ResourcesPlugin.getPlugin().getPluginPreferences().removePropertyChangeListener(this);
}
@Override
public void startup(IProgressMonitor monitor) {
Preferences preferences = ResourcesPlugin.getPlugin().getPluginPreferences();
preferences.addPropertyChangeListener(this);
lightweightAutoRefreshEnabled = preferences.getBoolean(ResourcesPlugin.PREF_LIGHTWEIGHT_AUTO_REFRESH);
}
/**
* The ResourceInfo must be mutable.
*/
public void updateLocalSync(ResourceInfo info, long localSyncInfo) {
info.setLocalSyncInfo(localSyncInfo);
if (localSyncInfo == I_NULL_SYNC_INFO)
info.clear(M_LOCAL_EXISTS);
else
info.set(M_LOCAL_EXISTS);
}
/**
* The target must exist in the workspace. The content InputStream is
* closed even if the method fails. If the force flag is false we only write
* the file if it does not exist or if it is already local and the timestamp
* has NOT changed since last synchronization, otherwise a CoreException
* is thrown.
*/
public void write(IFile target, InputStream content, IFileInfo fileInfo, int updateFlags, boolean append, IProgressMonitor monitor) throws CoreException {
SubMonitor subMonitor = SubMonitor.convert(monitor, 4);
try {
IFileStore store = getStore(target);
if (fileInfo.getAttribute(EFS.ATTRIBUTE_READ_ONLY)) {
String message = NLS.bind(Messages.localstore_couldNotWriteReadOnly, target.getFullPath());
throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, target.getFullPath(), message, null);
}
long lastModified = fileInfo.getLastModified();
if (BitMask.isSet(updateFlags, IResource.FORCE)) {
if (append && !target.isLocal(IResource.DEPTH_ZERO) && !fileInfo.exists()) {
// force=true, local=false, existsInFileSystem=false
String message = NLS.bind(Messages.resources_mustBeLocal, target.getFullPath());
throw new ResourceException(IResourceStatus.RESOURCE_NOT_LOCAL, target.getFullPath(), message, null);
}
} else {
if (target.isLocal(IResource.DEPTH_ZERO)) {
ResourceInfo info = ((Resource) target).getResourceInfo(true, false);
// test if timestamp is the same since last synchronization
if (lastModified != info.getLocalSyncInfo()) {
asyncRefresh(target);
String message = NLS.bind(Messages.localstore_resourceIsOutOfSync, target.getFullPath());
throw new ResourceException(IResourceStatus.OUT_OF_SYNC_LOCAL, target.getFullPath(), message, null);
}
if (!fileInfo.exists()) {
asyncRefresh(target);
String message = NLS.bind(Messages.localstore_resourceDoesNotExist, target.getFullPath());
throw new ResourceException(IResourceStatus.NOT_FOUND_LOCAL, target.getFullPath(), message, null);
}
} else {
if (fileInfo.exists()) {
String message = NLS.bind(Messages.localstore_resourceExists, target.getFullPath());
throw new ResourceException(IResourceStatus.EXISTS_LOCAL, target.getFullPath(), message, null);
}
if (append) {
String message = NLS.bind(Messages.resources_mustBeLocal, target.getFullPath());
throw new ResourceException(IResourceStatus.RESOURCE_NOT_LOCAL, target.getFullPath(), message, null);
}
}
}
// add entry to History Store.
if (BitMask.isSet(updateFlags, IResource.KEEP_HISTORY) && fileInfo.exists())
//never move to the history store, because then the file is missing if write fails
getHistoryStore().addState(target.getFullPath(), store, fileInfo, false);
if (!fileInfo.exists())
store.getParent().mkdir(EFS.NONE, null);
// On Windows an attempt to open an output stream on a hidden file results in FileNotFoundException.
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=194216
boolean restoreHiddenAttribute = false;
if (fileInfo.exists() && fileInfo.getAttribute(EFS.ATTRIBUTE_HIDDEN) && Platform.getOS().equals(Platform.OS_WIN32)) {
fileInfo.setAttribute(EFS.ATTRIBUTE_HIDDEN, false);
store.putInfo(fileInfo, EFS.SET_ATTRIBUTES, subMonitor.split(1));
restoreHiddenAttribute = true;
} else {
subMonitor.split(1);
}
int options = append ? EFS.APPEND : EFS.NONE;
OutputStream out = store.openOutputStream(options, subMonitor.split(1));
if (restoreHiddenAttribute) {
fileInfo.setAttribute(EFS.ATTRIBUTE_HIDDEN, true);
store.putInfo(fileInfo, EFS.SET_ATTRIBUTES, subMonitor.split(1));
} else {
subMonitor.split(1);
}
FileUtil.transferStreams(content, out, store.toString(), subMonitor.split(1));
// get the new last modified time and stash in the info
lastModified = store.fetchInfo().getLastModified();
ResourceInfo info = ((Resource) target).getResourceInfo(false, true);
updateLocalSync(info, lastModified);
info.incrementContentId();
info.clear(M_CONTENT_CACHE);
workspace.updateModificationStamp(info);
} finally {
FileUtil.safeClose(content);
}
}
/**
* If force is false, this method fails if there is already a resource in
* target's location.
*/
public void write(IFolder target, boolean force, IProgressMonitor monitor) throws CoreException {
IFileStore store = getStore(target);
if (!force) {
IFileInfo fileInfo = store.fetchInfo();
if (fileInfo.isDirectory()) {
String message = NLS.bind(Messages.localstore_resourceExists, target.getFullPath());
throw new ResourceException(IResourceStatus.EXISTS_LOCAL, target.getFullPath(), message, null);
}
if (fileInfo.exists()) {
String message = NLS.bind(Messages.localstore_fileExists, target.getFullPath());
throw new ResourceException(IResourceStatus.OUT_OF_SYNC_LOCAL, target.getFullPath(), message, null);
}
}
store.mkdir(EFS.NONE, monitor);
ResourceInfo info = ((Resource) target).getResourceInfo(false, true);
updateLocalSync(info, store.fetchInfo().getLastModified());
}
/**
* Write the .project file without modifying the resource tree. This is called
* during save when it is discovered that the .project file is missing. The tree
* cannot be modified during save.
*/
public void writeSilently(IProject target) throws CoreException {
IPath location = locationFor(target, false);
//if the project location cannot be resolved, we don't know if a description file exists or not
if (location == null)
return;
IFileStore projectStore = getStore(target);
projectStore.mkdir(EFS.NONE, null);
//can't do anything if there's no description
IProjectDescription desc = ((Project) target).internalGetDescription();
if (desc == null)
return;
//write the project's private description to the meta-data area
getWorkspace().getMetaArea().writePrivateDescription(target);
//write the file that represents the project description
IFileStore fileStore = projectStore.getChild(IProjectDescription.DESCRIPTION_FILE_NAME);
OutputStream out = null;
try {
out = fileStore.openOutputStream(EFS.NONE, null);
IFile file = target.getFile(IProjectDescription.DESCRIPTION_FILE_NAME);
new ModelObjectWriter().write(desc, out, FileUtil.getLineSeparator(file));
out.close();
} catch (IOException e) {
String msg = NLS.bind(Messages.resources_writeMeta, target.getFullPath());
throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, target.getFullPath(), msg, e);
} finally {
FileUtil.safeClose(out);
}
//for backwards compatibility, ensure the old .prj file is deleted
getWorkspace().getMetaArea().clearOldDescription(target);
}
}