| /******************************************************************************* |
| * Copyright (c) 2000, 2015 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Sergey Prigogin (Google) - [482064] Incorrect SubMonitor usage in RefreshLocalVisitor.visit |
| *******************************************************************************/ |
| package org.eclipse.core.internal.localstore; |
| |
| import org.eclipse.core.internal.resources.*; |
| import org.eclipse.core.internal.utils.Messages; |
| import org.eclipse.core.internal.utils.Policy; |
| import org.eclipse.core.resources.*; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.osgi.util.NLS; |
| |
| /** |
| * Visits a unified tree, and synchronizes the file system with the |
| * resource tree. After the visit is complete, the file system will |
| * be synchronized with the workspace tree with respect to |
| * resource existence, gender, and timestamp. |
| */ |
| public class RefreshLocalVisitor implements IUnifiedTreeVisitor, ILocalStoreConstants { |
| /** control constants */ |
| protected static final int RL_UNKNOWN = 0; |
| protected static final int RL_IN_SYNC = 1; |
| protected static final int RL_NOT_IN_SYNC = 2; |
| |
| // Progress monitor will initially move by 1. / TOTAL_WORK per resource but will gradually slow down |
| // as more resources are discovered. |
| public static final int TOTAL_WORK = 1000; |
| |
| protected MultiStatus errors; |
| protected SubMonitor monitor; |
| protected boolean resourceChanged; |
| protected Workspace workspace; |
| |
| public RefreshLocalVisitor(IProgressMonitor monitor) { |
| this.monitor = SubMonitor.convert(monitor); |
| workspace = (Workspace) ResourcesPlugin.getWorkspace(); |
| resourceChanged = false; |
| String msg = Messages.resources_errorMultiRefresh; |
| errors = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_LOCAL, msg, null); |
| } |
| |
| /** |
| * This method has the same implementation as resourceChanged but as they are different |
| * cases, we prefer to use different methods. |
| */ |
| protected void contentAdded(UnifiedTreeNode node, Resource target) { |
| resourceChanged(node, target); |
| } |
| |
| protected void createResource(UnifiedTreeNode node, Resource target) throws CoreException { |
| ResourceInfo info = target.getResourceInfo(false, false); |
| int flags = target.getFlags(info); |
| if (target.exists(flags, false)) |
| return; |
| /* make sure target's parent exists */ |
| IContainer parent = target.getParent(); |
| if (parent.getType() == IResource.FOLDER) |
| ((Folder) target.getParent()).ensureExists(monitor); |
| /* Use the basic file creation protocol since we don't want to create any content on disk. */ |
| info = workspace.createResource(target, false); |
| /* Mark this resource as having unknown children */ |
| info.set(ICoreConstants.M_CHILDREN_UNKNOWN); |
| target.getLocalManager().updateLocalSync(info, node.getLastModified()); |
| } |
| |
| protected void deleteResource(UnifiedTreeNode node, Resource target) throws CoreException { |
| ResourceInfo info = target.getResourceInfo(false, false); |
| int flags = target.getFlags(info); |
| //don't delete linked resources |
| if (ResourceInfo.isSet(flags, ICoreConstants.M_LINK)) { |
| //just clear local sync info |
| info = target.getResourceInfo(false, true); |
| //handle concurrent deletion |
| if (info != null) { |
| info.clearModificationStamp(); |
| target.getLocalManager().updateLocalSync(info, node.getLastModified()); |
| } |
| return; |
| } |
| if (target.exists(flags, false)) |
| target.deleteResource(true, errors); |
| node.setExistsWorkspace(false); |
| } |
| |
| protected void fileToFolder(UnifiedTreeNode node, Resource target) throws CoreException { |
| ResourceInfo info = target.getResourceInfo(false, false); |
| int flags = target.getFlags(info); |
| if (target.exists(flags, true)) { |
| target = (Folder) ((File) target).changeToFolder(); |
| } else { |
| if (!target.exists(flags, false)) { |
| target = (Resource) workspace.getRoot().getFolder(target.getFullPath()); |
| // Use the basic file creation protocol since we don't want to create any content on disk. |
| workspace.createResource(target, false); |
| } |
| } |
| node.setResource(target); |
| info = target.getResourceInfo(false, true); |
| target.getLocalManager().updateLocalSync(info, node.getLastModified()); |
| } |
| |
| protected void folderToFile(UnifiedTreeNode node, Resource target) throws CoreException { |
| ResourceInfo info = target.getResourceInfo(false, false); |
| int flags = target.getFlags(info); |
| if (target.exists(flags, true)) |
| target = (File) ((Folder) target).changeToFile(); |
| else { |
| if (!target.exists(flags, false)) { |
| target = (Resource) workspace.getRoot().getFile(target.getFullPath()); |
| // Use the basic file creation protocol since we don't want to |
| // create any content on disk. |
| workspace.createResource(target, false); |
| } |
| } |
| node.setResource(target); |
| info = target.getResourceInfo(false, true); |
| target.getLocalManager().updateLocalSync(info, node.getLastModified()); |
| } |
| |
| /** |
| * Returns the status of the nodes visited so far. This will be a multi-status |
| * that describes all problems that have occurred, or an OK status if everything |
| * went smoothly. |
| */ |
| public IStatus getErrorStatus() { |
| return errors; |
| } |
| |
| protected void makeLocal(UnifiedTreeNode node, Resource target) { |
| ResourceInfo info = target.getResourceInfo(false, true); |
| if (info != null) |
| target.getLocalManager().updateLocalSync(info, node.getLastModified()); |
| } |
| |
| /** |
| * Refreshes the parent of a resource currently being synchronized. |
| */ |
| protected void refresh(Container parent) throws CoreException { |
| parent.getLocalManager().refresh(parent, IResource.DEPTH_ZERO, false, null); |
| } |
| |
| protected void resourceChanged(UnifiedTreeNode node, Resource target) { |
| ResourceInfo info = target.getResourceInfo(false, true); |
| if (info == null) |
| return; |
| target.getLocalManager().updateLocalSync(info, node.getLastModified()); |
| info.incrementContentId(); |
| // forget content-related caching flags |
| info.clear(ICoreConstants.M_CONTENT_CACHE); |
| workspace.updateModificationStamp(info); |
| } |
| |
| public boolean resourcesChanged() { |
| return resourceChanged; |
| } |
| |
| /** |
| * deletion or creation -- Returns: |
| * - RL_IN_SYNC - the resource is in-sync with the file system |
| * - RL_NOT_IN_SYNC - the resource is not in-sync with file system |
| * - RL_UNKNOWN - couldn't determine the sync status for this resource |
| */ |
| protected int synchronizeExistence(UnifiedTreeNode node, Resource target) throws CoreException { |
| if (node.existsInWorkspace()) { |
| if (!node.existsInFileSystem()) { |
| // 1. non-local files are always in sync |
| // 2. links to non-existent locations with the modification stamp of IResource.NULL_STAMP are in sync |
| if (target.isLocal(IResource.DEPTH_ZERO) && target.getModificationStamp() != IResource.NULL_STAMP) { |
| deleteResource(node, target); |
| resourceChanged = true; |
| return RL_NOT_IN_SYNC; |
| } |
| return RL_IN_SYNC; |
| } |
| } else { |
| // do we have a gender variant in the workspace? |
| IResource genderVariant = workspace.getRoot().findMember(target.getFullPath()); |
| if (genderVariant != null) |
| return RL_UNKNOWN; |
| if (node.existsInFileSystem()) { |
| Container parent = (Container) target.getParent(); |
| if (!parent.exists()) { |
| refresh(parent); |
| if (!parent.exists()) |
| return RL_NOT_IN_SYNC; |
| } |
| if (!target.getName().equals(node.getLocalName())) |
| return RL_IN_SYNC; |
| if (!Workspace.caseSensitive && node.getLevel() == 0) { |
| // do we have any alphabetic variants in the workspace? |
| IResource variant = target.findExistingResourceVariant(target.getFullPath()); |
| if (variant != null) { |
| deleteResource(node, ((Resource) variant)); |
| createResource(node, target); |
| resourceChanged = true; |
| return RL_NOT_IN_SYNC; |
| } |
| } |
| createResource(node, target); |
| resourceChanged = true; |
| return RL_NOT_IN_SYNC; |
| } |
| } |
| return RL_UNKNOWN; |
| } |
| |
| /** |
| * gender change -- Returns true if gender was in sync. |
| */ |
| protected boolean synchronizeGender(UnifiedTreeNode node, Resource target) throws CoreException { |
| if (!node.existsInWorkspace()) { |
| //may be an existing resource in the workspace of different gender |
| IResource genderVariant = workspace.getRoot().findMember(target.getFullPath()); |
| if (genderVariant != null) |
| target = (Resource) genderVariant; |
| } |
| if (target.getType() == IResource.FILE) { |
| if (node.isFolder()) { |
| fileToFolder(node, target); |
| resourceChanged = true; |
| return false; |
| } |
| } else { |
| if (!node.isFolder()) { |
| folderToFile(node, target); |
| resourceChanged = true; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * lastModified |
| */ |
| protected void synchronizeLastModified(UnifiedTreeNode node, Resource target) { |
| if (target.isLocal(IResource.DEPTH_ZERO)) |
| resourceChanged(node, target); |
| else |
| contentAdded(node, target); |
| resourceChanged = true; |
| } |
| |
| @Override |
| public boolean visit(UnifiedTreeNode node) throws CoreException { |
| Policy.checkCanceled(monitor); |
| try { |
| if (node.isErrorInFileSystem()) |
| return false; // Don't visit children if we encountered an I/O error |
| Resource target = (Resource) node.getResource(); |
| int targetType = target.getType(); |
| if (targetType == IResource.PROJECT) |
| return true; |
| if (node.existsInWorkspace() && node.existsInFileSystem()) { |
| /* for folders we only care about updating local status */ |
| if (targetType == IResource.FOLDER && node.isFolder()) { |
| // if not local, mark as local |
| if (!target.isLocal(IResource.DEPTH_ZERO)) |
| makeLocal(node, target); |
| ResourceInfo info = target.getResourceInfo(false, false); |
| if (info != null && info.getModificationStamp() != IResource.NULL_STAMP) |
| return true; |
| } |
| /* compare file last modified */ |
| if (targetType == IResource.FILE && !node.isFolder()) { |
| ResourceInfo info = target.getResourceInfo(false, false); |
| if (info != null && info.getModificationStamp() != IResource.NULL_STAMP && info.getLocalSyncInfo() == node.getLastModified()) |
| return true; |
| } |
| } else { |
| if (node.existsInFileSystem() && !Path.EMPTY.isValidSegment(node.getLocalName())) { |
| String message = NLS.bind(Messages.resources_invalidResourceName, node.getLocalName()); |
| errors.merge(new ResourceStatus(IResourceStatus.INVALID_RESOURCE_NAME, message)); |
| return false; |
| } |
| int state = synchronizeExistence(node, target); |
| if (state == RL_IN_SYNC || state == RL_NOT_IN_SYNC) { |
| if (targetType == IResource.FILE) { |
| try { |
| ((File) target).updateMetadataFiles(); |
| } catch (CoreException e) { |
| errors.merge(e.getStatus()); |
| } |
| } |
| return true; |
| } |
| } |
| if (node.isSymbolicLink() && !node.existsInFileSystem()) |
| return true; // Dangling symbolic links are considered to be synchronized. |
| |
| if (synchronizeGender(node, target)) |
| synchronizeLastModified(node, target); |
| if (targetType == IResource.FILE) { |
| try { |
| ((File) target).updateMetadataFiles(); |
| } catch (CoreException e) { |
| errors.merge(e.getStatus()); |
| } |
| } |
| return true; |
| } finally { |
| // The monitor will asymptotically approach 100% as the number of processed resources increases. |
| monitor.setWorkRemaining(TOTAL_WORK).worked(1); |
| } |
| } |
| } |