/******************************************************************************* * Copyright (c) 2000, 2010 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 * Serge Beauchamp (Freescale Semiconductor) - [229633] Project Path Variable Support *******************************************************************************/ package org.eclipse.core.internal.resources; import java.net.URI; import org.eclipse.core.filesystem.*; import org.eclipse.core.filesystem.URIUtil; import org.eclipse.core.internal.localstore.FileSystemResourceManager; import org.eclipse.core.internal.properties.IPropertyManager; import org.eclipse.core.internal.utils.*; import org.eclipse.core.resources.*; import org.eclipse.core.resources.team.IResourceTree; import org.eclipse.core.runtime.*; import org.eclipse.core.runtime.jobs.ILock; import org.eclipse.osgi.util.NLS; /** * @since 2.0 * * Implementation note: Since the move/delete hook involves running third * party code, the workspace lock is not held. This means the workspace * lock must be re-acquired whenever we need to manipulate the workspace * in any way. All entry points from third party code back into the tree must * be done in an acquire/release pair. */ class ResourceTree implements IResourceTree { private boolean isValid = true; private final FileSystemResourceManager localManager; /** * The lock to acquire when the workspace needs to be manipulated */ private ILock lock; private MultiStatus multistatus; private int updateFlags; /** * Constructor for this class. */ public ResourceTree(FileSystemResourceManager localManager, ILock lock, MultiStatus status, int updateFlags) { super(); this.localManager = localManager; this.lock = lock; this.multistatus = status; this.updateFlags = updateFlags; } /** * @see IResourceTree#addToLocalHistory(IFile) */ public void addToLocalHistory(IFile file) { Assert.isLegal(isValid); try { lock.acquire(); if (!file.exists()) return; IFileStore store = localManager.getStore(file); final IFileInfo fileInfo = store.fetchInfo(); if (!fileInfo.exists()) return; localManager.getHistoryStore().addState(file.getFullPath(), store, fileInfo, false); } finally { lock.release(); } } private IFileStore computeDestinationStore(IProjectDescription destDescription) throws CoreException { URI destLocation = destDescription.getLocationURI(); // Use the default area if necessary for the destination. if (destLocation == null) { IPath rootLocation = ResourcesPlugin.getWorkspace().getRoot().getLocation(); destLocation = rootLocation.append(destDescription.getName()).toFile().toURI(); } return EFS.getStore(destLocation); } /** * @see IResourceTree#computeTimestamp(IFile) */ public long computeTimestamp(IFile file) { Assert.isLegal(isValid); try { lock.acquire(); if (!file.getProject().exists()) return NULL_TIMESTAMP; return internalComputeTimestamp(file); } finally { lock.release(); } } /** * Copies the local history of source to destination. Note that if source * is an IFolder, it is assumed that the same structure exists under destination * and the local history of any IFile under source will be copied to the * associated IFile under destination. */ private void copyLocalHistory(IResource source, IResource destination) { localManager.getHistoryStore().copyHistory(source, destination, true); } /** * @see IResourceTree#deletedFile(IFile) */ public void deletedFile(IFile file) { Assert.isLegal(isValid); try { lock.acquire(); // Do nothing if the resource doesn't exist. if (!file.exists()) return; try { // Delete properties, generate marker deltas, and remove the node from the workspace tree. ((Resource) file).deleteResource(true, null); } catch (CoreException e) { String message = NLS.bind(Messages.resources_errorDeleting, file.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, file.getFullPath(), message, e); failed(status); } } finally { lock.release(); } } /** * @see IResourceTree#deletedFolder(IFolder) */ public void deletedFolder(IFolder folder) { Assert.isLegal(isValid); try { lock.acquire(); // Do nothing if the resource doesn't exist. if (!folder.exists()) return; try { // Delete properties, generate marker deltas, and remove the node from the workspace tree. ((Resource) folder).deleteResource(true, null); } catch (CoreException e) { String message = NLS.bind(Messages.resources_errorDeleting, folder.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, folder.getFullPath(), message, e); failed(status); } } finally { lock.release(); } } /** * @see IResourceTree#deletedProject(IProject) */ public void deletedProject(IProject target) { Assert.isLegal(isValid); try { lock.acquire(); // Do nothing if the resource doesn't exist. if (!target.exists()) return; // Delete properties, generate marker deltas, and remove the node from the workspace tree. try { ((Project) target).deleteResource(false, null); } catch (CoreException e) { String message = NLS.bind(Messages.resources_errorDeleting, target.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, target.getFullPath(), message, e); // log the status but don't return until we try and delete the rest of the project info failed(status); } } finally { lock.release(); } } /** * Makes sure that the destination directory for a project move is unoccupied. * Returns true if successful, and false if the move should be aborted */ private boolean ensureDestinationEmpty(IProject source, IFileStore destinationStore, IProgressMonitor monitor) throws CoreException { String message; //Make sure the destination location is unoccupied if (!destinationStore.fetchInfo().exists()) return true; //check for existing children if (destinationStore.childNames(EFS.NONE, Policy.subMonitorFor(monitor, 0)).length > 0) { //allow case rename to proceed if (((Resource) source).getStore().equals(destinationStore)) return true; //fail because the destination is occupied message = NLS.bind(Messages.localstore_resourceExists, destinationStore); IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, null); failed(status); return false; } //delete the destination directory to allow for efficient renaming destinationStore.delete(EFS.NONE, Policy.subMonitorFor(monitor, 0)); return true; } /** * This operation has failed for the given reason. Add it to this * resource tree's status. */ public void failed(IStatus reason) { Assert.isLegal(isValid); multistatus.add(reason); } /** * Returns the status object held onto by this resource tree. */ protected IStatus getStatus() { return multistatus; } /** * @see IResourceTree#getTimestamp(IFile) */ public long getTimestamp(IFile file) { Assert.isLegal(isValid); try { lock.acquire(); if (!file.exists()) return NULL_TIMESTAMP; ResourceInfo info = ((File) file).getResourceInfo(false, false); return info == null ? NULL_TIMESTAMP : info.getLocalSyncInfo(); } finally { lock.release(); } } /** * Returns the local timestamp for a file. * * @param file * @return The local file system timestamp */ private long internalComputeTimestamp(IFile file) { IFileInfo fileInfo = localManager.getStore(file).fetchInfo(); return fileInfo.exists() ? fileInfo.getLastModified() : NULL_TIMESTAMP; } /** * Helper method for #standardDeleteFile. Returns a boolean indicating whether or * not the delete was successful. */ private boolean internalDeleteFile(IFile file, int flags, IProgressMonitor monitor) { try { String message = NLS.bind(Messages.resources_deleting, file.getFullPath()); monitor.beginTask(message, Policy.totalWork); Policy.checkCanceled(monitor); // Do nothing if the file doesn't exist in the workspace. if (!file.exists()) { // Indicate that the delete was successful. return true; } // Don't delete contents if this is a linked resource if (file.isLinked()) { deletedFile(file); return true; } // If the file doesn't exist on disk then signal to the workspace to delete the // file and return. IFileStore fileStore = localManager.getStore(file); boolean localExists = fileStore.fetchInfo().exists(); if (!localExists) { deletedFile(file); // Indicate that the delete was successful. return true; } boolean keepHistory = (flags & IResource.KEEP_HISTORY) != 0; boolean force = (flags & IResource.FORCE) != 0; // Add the file to the local history if requested by the user. if (keepHistory) addToLocalHistory(file); monitor.worked(Policy.totalWork / 4); // We want to fail if force is false and the file is not synchronized with the // local file system. if (!force) { boolean inSync = isSynchronized(file, IResource.DEPTH_ZERO); // only want to fail if the file still exists. if (!inSync && localExists) { message = NLS.bind(Messages.localstore_resourceIsOutOfSync, file.getFullPath()); IStatus status = new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, file.getFullPath(), message); failed(status); // Indicate that the delete was unsuccessful. return false; } } monitor.worked(Policy.totalWork / 4); // Try to delete the file from the file system. try { fileStore.delete(EFS.NONE, Policy.subMonitorFor(monitor, Policy.totalWork / 4)); // If the file was successfully deleted from the file system the // workspace tree should be updated accordingly. deletedFile(file); // Indicate that the delete was successful. return true; } catch (CoreException e) { message = NLS.bind(Messages.resources_couldnotDelete, fileStore.toString()); IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, file.getFullPath(), message, e); failed(status); } // Indicate that the delete was unsuccessful. return false; } finally { monitor.done(); } } /** * Helper method for #standardDeleteFolder. Returns a boolean indicating * whether or not the deletion of this folder was successful. Does a best effort * delete of this resource and its children. */ private boolean internalDeleteFolder(IFolder folder, int flags, IProgressMonitor monitor) { String message = NLS.bind(Messages.resources_deleting, folder.getFullPath()); monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ monitor.subTask(message); Policy.checkCanceled(monitor); // Do nothing if the folder doesn't exist in the workspace. if (!folder.exists()) return true; // Don't delete contents if this is a linked resource if (folder.isLinked()) { deletedFolder(folder); return true; } // If the folder doesn't exist on disk then update the tree and return. IFileStore fileStore = localManager.getStore(folder); if (!fileStore.fetchInfo().exists()) { deletedFolder(folder); return true; } try { //this will delete local and workspace localManager.delete(folder, flags, Policy.subMonitorFor(monitor, Policy.totalWork)); } catch (CoreException ce) { message = NLS.bind(Messages.localstore_couldnotDelete, folder.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, IResourceStatus.FAILED_DELETE_LOCAL, folder.getFullPath(), message, ce); failed(status); return false; } return true; } /** * Does a best-effort delete on this resource and all its children. */ private boolean internalDeleteProject(IProject project, int flags, IProgressMonitor monitor) { // Recursively delete each member of the project. IResource[] members = null; try { members = project.members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); } catch (CoreException e) { String message = NLS.bind(Messages.resources_errorMembers, project.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, project.getFullPath(), message, e); failed(status); // Indicate that the delete was unsuccessful. return false; } boolean deletedChildren = true; for (int i = 0; i < members.length; i++) { IResource child = members[i]; switch (child.getType()) { case IResource.FILE : // ignore the .project file for now and delete it last if (!IProjectDescription.DESCRIPTION_FILE_NAME.equals(child.getName())) deletedChildren &= internalDeleteFile((IFile) child, flags, Policy.subMonitorFor(monitor, Policy.totalWork / members.length)); break; case IResource.FOLDER : deletedChildren &= internalDeleteFolder((IFolder) child, flags, Policy.subMonitorFor(monitor, Policy.totalWork / members.length)); break; } } IFileStore projectStore = localManager.getStore(project); // Check to see if the children were deleted ok. If there was a problem // just return as the problem should have been logged by the recursive // call to the child. if (!deletedChildren) // Indicate that the delete was unsuccessful. return false; //Check if there are any undiscovered children of the project on disk other than description file String[] children; try { children = projectStore.childNames(EFS.NONE, null); } catch (CoreException e) { //treat failure to access the directory as a non-existent directory children = new String[0]; } boolean force = BitMask.isSet(flags, IResource.FORCE); if (!force && (children.length != 1 || !IProjectDescription.DESCRIPTION_FILE_NAME.equals(children[0]))) { String message = NLS.bind(Messages.localstore_resourceIsOutOfSync, project.getName()); failed(new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, project.getFullPath(), message)); return false; } //Now delete the project description file IResource file = project.findMember(IProjectDescription.DESCRIPTION_FILE_NAME); if (file == null) { //the .project have may have been recreated on disk automatically by snapshot IFileStore dotProject = projectStore.getChild(IProjectDescription.DESCRIPTION_FILE_NAME); try { dotProject.delete(EFS.NONE, null); } catch (CoreException e) { failed(e.getStatus()); } } else { boolean deletedProjectFile = internalDeleteFile((IFile) file, flags, Policy.monitorFor(null)); if (!deletedProjectFile) { String message = NLS.bind(Messages.resources_couldnotDelete, file.getFullPath()); IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, file.getFullPath(), message); failed(status); // Indicate that the delete was unsuccessful. return false; } } //children are deleted, so now delete the parent try { projectStore.delete(EFS.NONE, null); deletedProject(project); // Indicate that the delete was successful. return true; } catch (CoreException e) { String message = NLS.bind(Messages.resources_couldnotDelete, projectStore.toString()); IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message, e); failed(status); // Indicate that the delete was unsuccessful. return false; } } /** * Return <code>true</code> if there is a change in the content area for the project. */ private boolean isContentChange(IProject project, IProjectDescription destDescription) { IProjectDescription srcDescription = ((Project) project).internalGetDescription(); URI srcLocation = srcDescription.getLocationURI(); URI destLocation = destDescription.getLocationURI(); if (srcLocation == null || destLocation == null) return true; //don't use URIUtil because we want to treat case rename as a content change return !srcLocation.equals(destLocation); } /** * Return <code>true</code> if there is a change in the name of the project. */ private boolean isNameChange(IProject project, IProjectDescription description) { return !project.getName().equals(description.getName()); } /** * Refreshes the resource hierarchy with its children. In case of failure * adds an appropriate status to the resource tree's status. */ private void safeRefresh(IResource resource) { try { resource.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor()); } catch (CoreException ce) { IStatus status = new ResourceStatus(IStatus.ERROR, IResourceStatus.FAILED_DELETE_LOCAL, resource.getFullPath(), Messages.refresh_refreshErr, ce); failed(status); } } /** * @see IResourceTree#isSynchronized(IResource, int) */ public boolean isSynchronized(IResource resource, int depth) { try { lock.acquire(); return localManager.isSynchronized(resource, depth); } finally { lock.release(); } } /** * The specific operation for which this tree was created has completed and this tree * should not be used anymore. Ensure that this is the case by making it invalid. This * is checked by all API methods. */ void makeInvalid() { this.isValid = false; } /** * @see IResourceTree#movedFile(IFile, IFile) */ public void movedFile(IFile source, IFile destination) { Assert.isLegal(isValid); try { lock.acquire(); // Do nothing if the resource doesn't exist. if (!source.exists()) return; // If the destination already exists then we have a problem. if (destination.exists()) { String message = NLS.bind(Messages.resources_mustNotExist, destination.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message); // log the status but don't return until we try and move the rest of the resource information. failed(status); } // Move the resource's persistent properties. IPropertyManager propertyManager = ((Resource) source).getPropertyManager(); try { propertyManager.copy(source, destination, IResource.DEPTH_ZERO); propertyManager.deleteProperties(source, IResource.DEPTH_ZERO); } catch (CoreException e) { String message = NLS.bind(Messages.resources_errorPropertiesMove, source.getFullPath(), destination.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); // log the status but don't return until we try and move the rest of the resource information. failed(status); } // Move the node in the workspace tree. Workspace workspace = (Workspace) source.getWorkspace(); try { workspace.move((Resource) source, destination.getFullPath(), IResource.DEPTH_ZERO, updateFlags, false); } catch (CoreException e) { String message = NLS.bind(Messages.resources_errorMoving, source.getFullPath(), destination.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); // log the status but don't return until we try and move the rest of the resource information. failed(status); } // Generate the marker deltas. try { workspace.getMarkerManager().moved(source, destination, IResource.DEPTH_ZERO); } catch (CoreException e) { String message = NLS.bind(Messages.resources_errorMarkersDelete, source.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); failed(status); } // Copy the local history information copyLocalHistory(source, destination); } finally { lock.release(); } } /** * @see IResourceTree#movedFolderSubtree(IFolder, IFolder) */ public void movedFolderSubtree(IFolder source, IFolder destination) { Assert.isLegal(isValid); try { lock.acquire(); // Do nothing if the source resource doesn't exist. if (!source.exists()) return; // If the destination already exists then we have an error. if (destination.exists()) { String message = NLS.bind(Messages.resources_mustNotExist, destination.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message); failed(status); return; } // Move the folder properties. int depth = IResource.DEPTH_INFINITE; IPropertyManager propertyManager = ((Resource) source).getPropertyManager(); try { propertyManager.copy(source, destination, depth); propertyManager.deleteProperties(source, depth); } catch (CoreException e) { String message = NLS.bind(Messages.resources_errorPropertiesMove, source.getFullPath(), destination.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); // log the status but don't return until we try and move the rest of the resource info failed(status); } // Create the destination node in the tree. Workspace workspace = (Workspace) source.getWorkspace(); try { workspace.move((Resource) source, destination.getFullPath(), depth, updateFlags, false); } catch (CoreException e) { String message = NLS.bind(Messages.resources_errorMoving, source.getFullPath(), destination.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); // log the status but don't return until we try and move the rest of the resource info failed(status); } // Generate the marker deltas. try { workspace.getMarkerManager().moved(source, destination, depth); } catch (CoreException e) { String message = NLS.bind(Messages.resources_errorMarkersDelete, source.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); failed(status); } // Copy the local history for this folder copyLocalHistory(source, destination); } finally { lock.release(); } } /** * @see IResourceTree#movedProjectSubtree(IProject, IProjectDescription) */ public boolean movedProjectSubtree(IProject project, IProjectDescription destDescription) { Assert.isLegal(isValid); try { lock.acquire(); // Do nothing if the source resource doesn't exist. if (!project.exists()) return true; Project source = (Project) project; Project destination = (Project) source.getWorkspace().getRoot().getProject(destDescription.getName()); Workspace workspace = (Workspace) source.getWorkspace(); int depth = IResource.DEPTH_INFINITE; // If the name of the source and destination projects are not the same then // rename the meta area and make changes in the tree. if (isNameChange(source, destDescription)) { if (destination.exists()) { String message = NLS.bind(Messages.resources_mustNotExist, destination.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message); failed(status); return false; } // Rename the project metadata area. Close the property store to flush everything to disk try { source.getPropertyManager().closePropertyStore(source); localManager.getHistoryStore().closeHistoryStore(source); } catch (CoreException e) { String message = NLS.bind(Messages.properties_couldNotClose, source.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); // log the status but don't return until we try and move the rest of the resource info failed(status); } final IFileSystem fileSystem = EFS.getLocalFileSystem(); IFileStore oldMetaArea = fileSystem.getStore(workspace.getMetaArea().locationFor(source)); IFileStore newMetaArea = fileSystem.getStore(workspace.getMetaArea().locationFor(destination)); try { oldMetaArea.move(newMetaArea, EFS.NONE, new NullProgressMonitor()); } catch (CoreException e) { String message = NLS.bind(Messages.resources_moveMeta, oldMetaArea, newMetaArea); IStatus status = new ResourceStatus(IResourceStatus.FAILED_WRITE_METADATA, destination.getFullPath(), message, e); // log the status but don't return until we try and move the rest of the resource info failed(status); } // Move the workspace tree. try { workspace.move(source, destination.getFullPath(), depth, updateFlags, true); } catch (CoreException e) { String message = NLS.bind(Messages.resources_errorMoving, source.getFullPath(), destination.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); // log the status but don't return until we try and move the rest of the resource info failed(status); } // Clear stale state on the destination project. ((ProjectInfo) destination.getResourceInfo(false, true)).fixupAfterMove(); // Generate marker deltas. try { workspace.getMarkerManager().moved(source, destination, depth); } catch (CoreException e) { String message = NLS.bind(Messages.resources_errorMarkersMove, source.getFullPath(), destination.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); // log the status but don't return until we try and move the rest of the resource info failed(status); } // Copy the local history copyLocalHistory(source, destination); } // Write the new project description on the destination project. try { //moving linked resources may have modified the description in memory ((ProjectDescription) destDescription).setLinkDescriptions(destination.internalGetDescription().getLinks()); // moving filters may have modified the description in memory ((ProjectDescription) destDescription).setFilterDescriptions(destination.internalGetDescription().getFilters()); // moving variables may have modified the description in memory ((ProjectDescription) destDescription).setVariableDescriptions(destination.internalGetDescription().getVariables()); destination.internalSetDescription(destDescription, true); destination.writeDescription(IResource.FORCE); } catch (CoreException e) { String message = Messages.resources_projectDesc; IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message, e); failed(status); } // write the private project description, including the project location try { workspace.getMetaArea().writePrivateDescription(destination); } catch (CoreException e) { failed(e.getStatus()); } // Do a refresh on the destination project to pick up any newly discovered resources try { destination.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor()); } catch (CoreException e) { String message = NLS.bind(Messages.resources_errorRefresh, destination.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message, e); failed(status); return false; } return true; } finally { lock.release(); } } /** * Helper method for moving the project content. Determines the content location * based on the project description. (default location or user defined?) */ private void moveProjectContent(IProject source, IFileStore destStore, int flags, IProgressMonitor monitor) throws CoreException { try { String message = NLS.bind(Messages.resources_moving, source.getFullPath()); monitor.beginTask(message, 10); IProjectDescription srcDescription = source.getDescription(); URI srcLocation = srcDescription.getLocationURI(); // If the locations are the same (and non-default) then there is nothing to do. if (srcLocation != null && URIUtil.equals(srcLocation, destStore.toURI())) return; //If this is a replace, just make sure the destination location exists, and return boolean replace = (flags & IResource.REPLACE) != 0; if (replace) { destStore.mkdir(EFS.NONE, Policy.subMonitorFor(monitor, 10)); return; } // Move the contents on disk. localManager.move(source, destStore, flags, Policy.subMonitorFor(monitor, 9)); //if this is a deep move, move the contents of any linked resources if ((flags & IResource.SHALLOW) == 0) { IResource[] children = source.members(); for (int i = 0; i < children.length; i++) { if (children[i].isLinked()) { message = NLS.bind(Messages.resources_moving, children[i].getFullPath()); monitor.subTask(message); IFileStore linkDestination = destStore.getChild(children[i].getName()); try { localManager.move(children[i], linkDestination, flags, Policy.monitorFor(null)); } catch (CoreException ce) { //log the failure, but keep trying on remaining links failed(ce.getStatus()); } } } } monitor.worked(1); } finally { monitor.done(); } } /** * @see IResourceTree#standardDeleteFile(IFile, int, IProgressMonitor) */ public void standardDeleteFile(IFile file, int flags, IProgressMonitor monitor) { Assert.isLegal(isValid); try { lock.acquire(); internalDeleteFile(file, flags, monitor); } finally { lock.release(); } } /** * @see IResourceTree#standardDeleteFolder(IFolder, int, IProgressMonitor) */ public void standardDeleteFolder(IFolder folder, int flags, IProgressMonitor monitor) { Assert.isLegal(isValid); try { lock.acquire(); internalDeleteFolder(folder, flags, monitor); } catch (OperationCanceledException oce) { safeRefresh(folder); throw oce; } finally { lock.release(); monitor.done(); } } /** * @see IResourceTree#standardDeleteProject(IProject, int, IProgressMonitor) */ public void standardDeleteProject(IProject project, int flags, IProgressMonitor monitor) { Assert.isLegal(isValid); try { lock.acquire(); String message = NLS.bind(Messages.resources_deleting, project.getFullPath()); monitor.beginTask(message, Policy.totalWork); // Do nothing if the project doesn't exist in the workspace tree. if (!project.exists()) return; boolean alwaysDeleteContent = (flags & IResource.ALWAYS_DELETE_PROJECT_CONTENT) != 0; boolean neverDeleteContent = (flags & IResource.NEVER_DELETE_PROJECT_CONTENT) != 0; boolean success = true; // Delete project content. Don't do anything if the user specified explicitly asked // not to delete the project content or if the project is closed and // ALWAYS_DELETE_PROJECT_CONTENT was not specified. if (alwaysDeleteContent || (project.isOpen() && !neverDeleteContent)) { // Force is implied if alwaysDeleteContent is true or if the project is in sync // with the local file system. if (alwaysDeleteContent || isSynchronized(project, IResource.DEPTH_INFINITE)) { flags |= IResource.FORCE; } // If the project is open we have to recursively try and delete all the files doing best-effort. if (project.isOpen()) { success = internalDeleteProject(project, flags, monitor); if (!success) { IFileStore store = localManager.getStore(project); message = NLS.bind(Messages.resources_couldnotDelete, store.toString()); IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message); failed(status); } return; } // If the project is closed we can short circuit this operation and delete all the files on disk. // The .project file is deleted at the end of the operation. try { IFileStore projectStore = localManager.getStore(project); IFileStore members[] = projectStore.childStores(EFS.NONE, null); for (int i = 0; i < members.length; i++) { if (!IProjectDescription.DESCRIPTION_FILE_NAME.equals(members[i].getName())) members[i].delete(EFS.NONE, Policy.subMonitorFor(monitor, Policy.totalWork * 7 / 8 / members.length)); } projectStore.delete(EFS.NONE, Policy.subMonitorFor(monitor, Policy.totalWork * 7 / 8 / (members.length > 0 ? members.length : 1))); } catch (OperationCanceledException oce) { safeRefresh(project); throw oce; } catch (CoreException ce) { message = NLS.bind(Messages.localstore_couldnotDelete, project.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message, ce); failed(status); return; } } // Signal that the workspace tree should be updated that the project has been deleted. if (success) deletedProject(project); else { message = NLS.bind(Messages.localstore_couldnotDelete, project.getFullPath()); IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message); failed(status); } } finally { lock.release(); monitor.done(); } } /** * @see IResourceTree#standardMoveFile(IFile, IFile, int, IProgressMonitor) */ public void standardMoveFile(IFile source, IFile destination, int flags, IProgressMonitor monitor) { Assert.isLegal(isValid); try { lock.acquire(); String message = NLS.bind(Messages.resources_moving, source.getFullPath()); monitor.subTask(message); // These pre-conditions should all be ok but just in case... if (!source.exists() || destination.exists() || !destination.getParent().isAccessible()) throw new IllegalArgumentException(); boolean force = (flags & IResource.FORCE) != 0; boolean keepHistory = (flags & IResource.KEEP_HISTORY) != 0; boolean isDeep = (flags & IResource.SHALLOW) == 0; // If the file is not in sync with the local file system and force is false, // then signal that we have an error. if (!force && !isSynchronized(source, IResource.DEPTH_INFINITE)) { message = NLS.bind(Messages.localstore_resourceIsOutOfSync, source.getFullPath()); IStatus status = new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, source.getFullPath(), message); failed(status); return; } monitor.worked(Policy.totalWork / 4); // Add the file contents to the local history if requested by the user. if (keepHistory) addToLocalHistory(source); monitor.worked(Policy.totalWork / 4); //for shallow move of linked resources, nothing needs to be moved in the file system if (!isDeep && source.isLinked()) { movedFile(source, destination); return; } // If the file was successfully moved in the file system then the workspace // tree needs to be updated accordingly. Otherwise signal that we have an error. IFileStore destStore = null; boolean failedDeletingSource = false; try { destStore = localManager.getStore(destination); //ensure parent of destination exists destStore.getParent().mkdir(EFS.NONE, Policy.subMonitorFor(monitor, 0)); localManager.move(source, destStore, flags, monitor); } catch (CoreException e) { failed(e.getStatus()); // did the fail occur after copying to the destination? failedDeletingSource = destStore != null && destStore.fetchInfo().exists(); // if so, we should proceed if (!failedDeletingSource) return; } movedFile(source, destination); updateMovedFileTimestamp(destination, internalComputeTimestamp(destination)); if (failedDeletingSource) { //recreate source file to ensure we are not out of sync try { source.refreshLocal(IResource.DEPTH_INFINITE, null); } catch (CoreException e) { //ignore secondary failure - we have already logged the main failure } } monitor.worked(Policy.totalWork / 4); return; } finally { lock.release(); monitor.done(); } } /** * @see IResourceTree#standardMoveFolder(IFolder, IFolder, int, IProgressMonitor) */ public void standardMoveFolder(IFolder source, IFolder destination, int flags, IProgressMonitor monitor) { Assert.isLegal(isValid); try { lock.acquire(); String message = NLS.bind(Messages.resources_moving, source.getFullPath()); monitor.beginTask(message, 100); // These pre-conditions should all be ok but just in case... if (!source.exists() || destination.exists() || !destination.getParent().isAccessible()) throw new IllegalArgumentException(); // Check to see if we are synchronized with the local file system. If we are in sync then we can // short circuit this method and do a file system only move. Otherwise we have to recursively // try and move all resources, doing it in a best-effort manner. boolean force = (flags & IResource.FORCE) != 0; if (!force && !isSynchronized(source, IResource.DEPTH_INFINITE)) { message = NLS.bind(Messages.localstore_resourceIsOutOfSync, source.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message); failed(status); return; } monitor.worked(20); //for linked resources, nothing needs to be moved in the file system boolean isDeep = (flags & IResource.SHALLOW) == 0; if (!isDeep && (source.isLinked() || source.isVirtual())) { movedFolderSubtree(source, destination); return; } // Move the resources in the file system. Only the FORCE flag is valid here so don't // have to worry about clearing the KEEP_HISTORY flag. IFileStore destStore = null; boolean failedDeletingSource = false; try { destStore = localManager.getStore(destination); localManager.move(source, destStore, flags, Policy.subMonitorFor(monitor, 60)); } catch (CoreException e) { failed(e.getStatus()); // did the fail occur after copying to the destination? failedDeletingSource = destStore != null && destStore.fetchInfo().exists(); // if so, we should proceed if (!failedDeletingSource) return; } movedFolderSubtree(source, destination); monitor.worked(20); updateTimestamps(destination, isDeep); if (failedDeletingSource) { //the move could have been partially successful, so refresh to ensure we are in sync try { source.refreshLocal(IResource.DEPTH_INFINITE, null); destination.refreshLocal(IResource.DEPTH_INFINITE, null); } catch (CoreException e) { //ignore secondary failures -we have already logged main failure } } } finally { lock.release(); monitor.done(); } } /** * @see IResourceTree#standardMoveProject(IProject, IProjectDescription, int, IProgressMonitor) */ public void standardMoveProject(IProject source, IProjectDescription description, int flags, IProgressMonitor monitor) { Assert.isLegal(isValid); try { lock.acquire(); String message = NLS.bind(Messages.resources_moving, source.getFullPath()); monitor.beginTask(message, Policy.totalWork); // Double-check this pre-condition. if (!source.isAccessible()) throw new IllegalArgumentException(); // If there is nothing to do on disk then signal to make the workspace tree // changes. if (!isContentChange(source, description)) { movedProjectSubtree(source, description); return; } // Check to see if we are synchronized with the local file system. boolean force = (flags & IResource.FORCE) != 0; if (!force && !isSynchronized(source, IResource.DEPTH_INFINITE)) { // FIXME: make this a best effort move? message = NLS.bind(Messages.localstore_resourceIsOutOfSync, source.getFullPath()); IStatus status = new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, source.getFullPath(), message); failed(status); return; } IFileStore destinationStore; try { destinationStore = computeDestinationStore(description); //destination can be non-empty on replace if ((flags & IResource.REPLACE) == 0) if (!ensureDestinationEmpty(source, destinationStore, monitor)) return; } catch (CoreException e) { //must fail if the destination location cannot be accessd (undefined file system) message = NLS.bind(Messages.localstore_couldNotMove, source.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); failed(status); return; } // Move the project content in the local file system. try { moveProjectContent(source, destinationStore, flags, Policy.subMonitorFor(monitor, Policy.totalWork * 3 / 4)); } catch (CoreException e) { message = NLS.bind(Messages.localstore_couldNotMove, source.getFullPath()); IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e); failed(status); //refresh the project because it might have been partially moved try { source.refreshLocal(IResource.DEPTH_INFINITE, null); } catch (CoreException e2) { //ignore secondary failures } } // If we got this far the project content has been moved on disk (if necessary) // and we need to update the workspace tree. movedProjectSubtree(source, description); monitor.worked(Policy.totalWork * 1 / 8); boolean isDeep = (flags & IResource.SHALLOW) == 0; updateTimestamps(source.getWorkspace().getRoot().getProject(description.getName()), isDeep); monitor.worked(Policy.totalWork * 1 / 8); } finally { lock.release(); monitor.done(); } } /** * @see IResourceTree#updateMovedFileTimestamp(IFile, long) */ public void updateMovedFileTimestamp(IFile file, long timestamp) { Assert.isLegal(isValid); try { lock.acquire(); // Do nothing if the file doesn't exist in the workspace tree. if (!file.exists()) return; // Update the timestamp in the tree. ResourceInfo info = ((Resource) file).getResourceInfo(false, true); // The info should never be null since we just checked that the resource exists in the tree. localManager.updateLocalSync(info, timestamp); //remove the linked bit since this resource has been moved in the file system info.clear(ICoreConstants.M_LINK); } finally { lock.release(); } } /** * Helper method to update all the timestamps in the tree to match * those in the file system. Used after a #move. */ private void updateTimestamps(IResource root, final boolean isDeep) { IResourceVisitor visitor = new IResourceVisitor() { public boolean visit(IResource resource) { if (resource.isLinked()) { if (isDeep && !((Resource) resource).isUnderVirtual()) { //clear the linked resource bit, if any ResourceInfo info = ((Resource) resource).getResourceInfo(false, true); info.clear(ICoreConstants.M_LINK); } return true; } //only needed if underlying file system does not preserve timestamps // if (resource.getType() == IResource.FILE) { // IFile file = (IFile) resource; // updateMovedFileTimestamp(file, computeTimestamp(file)); // } return true; } }; try { root.accept(visitor, IResource.DEPTH_INFINITE, IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); } catch (CoreException e) { // No exception should be thrown. } } }