blob: c04708776e10606cdd346b86de39ec724391aa46 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2006 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
*******************************************************************************/
package org.eclipse.team.internal.ccvs.core.util;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Path;
import org.eclipse.team.internal.ccvs.core.*;
import org.eclipse.team.internal.ccvs.core.CVSException;
import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin;
import org.eclipse.team.internal.ccvs.core.ICVSFile;
import org.eclipse.team.internal.ccvs.core.Policy;
import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
import org.eclipse.team.internal.ccvs.core.resources.EclipseSynchronizer;
import org.eclipse.team.internal.ccvs.core.syncinfo.DeferredResourceChangeHandler;
/*
* Listens to CVS meta-file changes and notifies the EclipseSynchronizer of changes made to sync files
* by 3rd parties.
*
* If CVS meta-directories are created outside of the CVS plugin their team-private state will be set
* by this listener however this change won't be known to other plugins because it does not generate
* a delta. As a result views, such as the navigator, may show CVS team-private directories. There
* are some common scenarios where a user may (depending on the order of delta traversal) see
* this behavior:
*
* 1. A user has an existing CVS project outside of Eclipse. By creating the project in Eclipse to point
* to the existing location the project's contents will be brought into Eclipse and the CVS folders
* will be marlked as team-private but other delta listeners that have handled the event already won't receive
* notification that the resource is now team-private. As a result, the user may have to close views or
* restart the workbench to have the CVS folders filtered.
*
* 2. A user performs CVS command line operations outside of Eclipse that result in new CVS folders.
* From Eclipse the refresh local will bring in the new folders and they will be marked as team-private.
* But as in 1, they may not appear in the UI.
*
* See: http://dev.eclipse.org/bugs/show_bug.cgi?id=12386
*/
public class SyncFileChangeListener implements IResourceChangeListener {
// consider the following changes types and ignore the others (e.g. marker and description changes are ignored)
protected int INTERESTING_CHANGES = IResourceDelta.CONTENT |
IResourceDelta.MOVED_FROM |
IResourceDelta.MOVED_TO |
IResourceDelta.OPEN |
IResourceDelta.REPLACED |
IResourceDelta.TYPE;
protected boolean isProjectOpening = false;
protected static DeferredResourceChangeHandler deferredHandler = new DeferredResourceChangeHandler();
/**
* This accessor is for use by test cases only.
*
* @return Returns the deferredHandler.
*/
public static DeferredResourceChangeHandler getDeferredHandler() {
return deferredHandler;
}
/*
* When a resource changes this method will detect if the changed resources is a meta file that has changed
* by a 3rd party. For example, if the command line tool was run and then the user refreshed from local. To
* distinguish changes made by this class and thoses made by others a modification stamp is persisted with each
* metafile.
*
* @see IResourceChangeListener#resourceChanged(IResourceChangeEvent)
*/
public void resourceChanged(IResourceChangeEvent event) {
try {
final Set changedContainers = new HashSet();
final Set externalDeletions = new HashSet();
setProjectOpening(false);
event.getDelta().accept(new IResourceDeltaVisitor() {
public boolean visit(IResourceDelta delta) {
IResource resource = delta.getResource();
if(resource.getType()==IResource.ROOT) {
// continue with the delta
return true;
}
if (resource.getType() == IResource.PROJECT) {
// If the project is not accessible, don't process it
if (!resource.isAccessible()) return false;
setProjectOpening((delta.getFlags() & IResourceDelta.OPEN) != 0);
}
String name = resource.getName();
int kind = delta.getKind();
// if the file has changed but not in a way that we care
// then ignore the change (e.g. marker changes to files).
if(kind == IResourceDelta.CHANGED &&
(delta.getFlags() & INTERESTING_CHANGES) == 0) {
return true;
}
if(name.equals(SyncFileWriter.CVS_DIRNAME)) {
handleCVSDir((IContainer)resource, kind);
// if the project is opening there is no need to notify about chagned CVs/ meta files
// they will all be read from disk.
if(isProjectOpening()) return false;
} else {
// Inform the synchronizer about folder creations
if(isProjectOpening()) return true;
}
if(isMetaFile(resource)) {
IResource[] toBeNotified = handleChangedMetaFile(resource);
if(toBeNotified.length>0 && isModifiedBy3rdParty(resource)) {
for (int i = 0; i < toBeNotified.length; i++) {
changedContainers.add(toBeNotified[i]);
}
if(Policy.DEBUG_METAFILE_CHANGES) {
System.out.println("[cvs] metafile changed by 3rd party: " + resource.getFullPath()); //$NON-NLS-1$
}
return false; /*don't visit any children we have all the information we need*/
}
} else if(isIgnoreFile(resource) && isModifiedBy3rdParty(resource)) {
deferredHandler.ignoreFileChanged((IFile)resource);
} else if (isExternalDeletion(resource, kind)) {
externalDeletions.add(resource);
} else if (kind == IResourceDelta.ADDED && isRecreation(resource)) {
deferredHandler.recreated(resource);
}
return true;
}
}, IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS);
if(!changedContainers.isEmpty() || !externalDeletions.isEmpty()) {
EclipseSynchronizer.getInstance().syncFilesChangedExternally(
(IContainer[])changedContainers.toArray(new IContainer[changedContainers.size()]),
(IFile[]) externalDeletions.toArray(new IFile[externalDeletions.size()]));
}
} catch(CoreException e) {
CVSProviderPlugin.log(e);
}
}
/**
* Treat a resource as an external deletion if
* - it is a file
* - the delta says the file was removed
* - the file is not managed but its parent is a CVS folder
*
* There will be some false positives but the reaction to this situation
* is to purge the cahced CVS meta-information so nothing bad will happen
* for the false positives.
*
* @param resource
* @param kind
* @return
*/
protected boolean isExternalDeletion(IResource resource, int kind) {
if (kind != IResourceDelta.REMOVED) return false;
if (resource.getType() != IResource.FILE) return false;
ICVSFile file = CVSWorkspaceRoot.getCVSFileFor((IFile)resource);
try {
return (!file.isManaged() && file.getParent().isCVSFolder() && file.getParent().exists());
} catch (CVSException e) {
CVSProviderPlugin.log(e);
return false;
}
}
/*
* Consider non-existing resources as being recently deleted and thus modified, and resources
* with modification stamps that differ from when the CVS plugin last modified the meta-file.
*/
protected boolean isModifiedBy3rdParty(IResource resource) {
if(!resource.exists()) return true;
long modStamp = resource.getModificationStamp();
Long whenWeWrote;
try {
whenWeWrote = (Long)resource.getSessionProperty(SyncFileWriter.MODSTAMP_KEY);
} catch(CoreException e) {
CVSProviderPlugin.log(e);
whenWeWrote = null;
}
return (whenWeWrote==null || whenWeWrote.longValue() != modStamp);
}
/*
* If it's a new CVS directory with the canonical child metafiles then mark it as team-private. Otherwise
* if changed or deleted
*/
protected void handleCVSDir(IContainer cvsDir, int kind) {
if((kind & IResourceDelta.ALL_WITH_PHANTOMS)!=0) {
if(kind==IResourceDelta.ADDED) {
// should this dir be made team-private? If it contains CVS/Root and CVS/Repository then yes!
IFile rootFile = cvsDir.getFile(new Path(SyncFileWriter.ROOT));
IFile repositoryFile = cvsDir.getFile(new Path(SyncFileWriter.REPOSITORY));
if(rootFile.exists() && repositoryFile.exists() && !cvsDir.isTeamPrivateMember()) {
try {
// TODO: Is this considered a tree modification?
cvsDir.setTeamPrivateMember(true);
if(Policy.DEBUG_METAFILE_CHANGES) {
System.out.println("[cvs] found a new CVS meta folder, marking as team-private: " + cvsDir.getFullPath()); //$NON-NLS-1$
}
} catch(CoreException e) {
CVSProviderPlugin.log(CVSException.wrapException(cvsDir, CVSMessages.SyncFileChangeListener_errorSettingTeamPrivateFlag, e));
}
}
}
}
}
protected boolean isIgnoreFile(IResource resource) {
return resource.getType() == IResource.FILE &&
resource.getName().equals(SyncFileWriter.IGNORE_FILE);
}
private boolean isRecreation(IResource resource) {
return EclipseSynchronizer.getInstance().wasPhantom(resource);
}
/*
* It's a meta file if it's parent is a team-private CVS folder.
*/
protected boolean isMetaFile(IResource resource) {
IContainer parent = resource.getParent();
return resource.getType() == IResource.FILE &&
parent!=null &&
parent.getName().equals(SyncFileWriter.CVS_DIRNAME) &&
(parent.isTeamPrivateMember() || !parent.exists());
}
/*
* This is a meta file (e.g. folder/CVS/Entries), notify that 'folder' and it's immediate children
* may have their CVS sync state changed. If the 'folder' is deleted than no notification is
* required.
*/
protected IContainer[] handleChangedMetaFile(IResource resource) {
IContainer changedContainer = resource.getParent().getParent();
if(changedContainer.exists()) {
return new IContainer[] {changedContainer};
} else {
return new IContainer[0];
}
}
/**
* @return boolean
*/
public boolean isProjectOpening() {
return isProjectOpening;
}
/**
* Sets the isProjectOpening.
* @param isProjectOpening The isProjectOpening to set
*/
public void setProjectOpening(boolean isProjectOpening) {
this.isProjectOpening = isProjectOpening;
}
}