| /******************************************************************************* |
| * 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); |
| } |
| } |