blob: a4e132cffda4be930448c9822e388c015979d67a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2002 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v0.5
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v05.html
*
* Contributors:
* IBM - Initial implementation
******************************************************************************/
package org.eclipse.team.internal.ccvs.core.resources;
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.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.ISaveContext;
import org.eclipse.core.resources.ISaveParticipant;
import org.eclipse.core.resources.ISynchronizer;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.internal.ccvs.core.CVSException;
import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin;
import org.eclipse.team.internal.ccvs.core.Policy;
import org.eclipse.team.internal.ccvs.core.syncinfo.FolderSyncInfo;
import org.eclipse.team.internal.ccvs.core.util.SyncFileWriter;
/**
* This cache uses session properties to hold the bytes representing the sync
* info. In addition when the workbench closes or a project is closed, the dirty
* state for all cvs managed folders are persisted using the resource's plugin
* synchronizer.
*/
/*package*/ class SessionPropertySyncInfoCache extends SyncInfoCache implements ISaveParticipant {
// key used on a folder to indicate that the resource sync has been cahced for it's children
private static final QualifiedName RESOURCE_SYNC_CACHED_KEY = new QualifiedName(CVSProviderPlugin.ID, "resource-sync-cached"); //$NON-NLS-1$
private static final QualifiedName FOLDER_SYNC_RESTORED_KEY = new QualifiedName(CVSProviderPlugin.ID, "folder-sync-restored"); //$NON-NLS-1$
private static final Object RESOURCE_SYNC_CACHED = new Object();
private static final Object FOLDER_SYNC_RESTORED = new Object();
/*package*/ static final String[] NULL_IGNORES = new String[0];
private static final FolderSyncInfo NULL_FOLDER_SYNC_INFO = new FolderSyncInfo("", "", null, false); //$NON-NLS-1$ //$NON-NLS-2$
/*package*/ SessionPropertySyncInfoCache() {
try {
// this save participant is removed when the plugin is shutdown.
ResourcesPlugin.getWorkspace().addSaveParticipant(CVSProviderPlugin.getPlugin(), this);
} catch (CoreException e) {
CVSProviderPlugin.log(e.getStatus());
}
}
/**
* If not already cached, loads and caches the folder ignores sync for the container.
* Folder must exist and must not be the workspace root.
*
* @param container the container
* @return the folder ignore patterns, or an empty array if none
*/
/*package*/ String[] cacheFolderIgnores(IContainer container) throws CVSException {
try {
// don't try to load if the information is already cached
String[] ignores = (String[])container.getSessionProperty(IGNORE_SYNC_KEY);
if (ignores == null) {
// read folder ignores and remember it
ignores = SyncFileWriter.readCVSIgnoreEntries(container);
if (ignores == null) ignores = NULL_IGNORES;
container.setSessionProperty(IGNORE_SYNC_KEY, ignores);
}
return ignores;
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
/*package*/ boolean isFolderSyncInfoCached(IContainer container) throws CVSException {
try {
return container.getSessionProperty(FOLDER_SYNC_KEY) != null;
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
/*package*/ boolean isResourceSyncInfoCached(IContainer container) throws CVSException {
try {
return container.getSessionProperty(RESOURCE_SYNC_CACHED_KEY) != null;
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
/*package*/ void setResourceSyncInfoCached(IContainer container) throws CVSException {
try {
container.setSessionProperty(RESOURCE_SYNC_CACHED_KEY, RESOURCE_SYNC_CACHED);
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
/**
* Returns the folder sync info for the container; null if none.
* Folder must exist and must not be the workspace root.
* The folder sync info for the container MUST ALREADY BE CACHED.
*
* @param container the container
* @return the folder sync info for the folder, or null if none.
* @see #cacheFolderSync
*/
/*package*/ FolderSyncInfo getCachedFolderSync(IContainer container) throws CVSException {
if (!container.exists()) return null;
try {
FolderSyncInfo info = (FolderSyncInfo)container.getSessionProperty(FOLDER_SYNC_KEY);
if (info == null) {
// There should be sync info but it was missing. Report the error
throw new CVSException(Policy.bind("EclipseSynchronizer.folderSyncInfoMissing", container.getFullPath().toString())); //$NON-NLS-1$
}
if (info == NULL_FOLDER_SYNC_INFO) return null;
return info;
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
/**
* Purges the cache recursively for all resources beneath the container.
* There must not be any pending uncommitted changes.
*/
/*package*/ void purgeCache(IContainer container, boolean deep) throws CVSException {
if (! container.exists()) return;
try {
if (container.getType() != IResource.ROOT) {
container.setSessionProperty(IGNORE_SYNC_KEY, null);
container.setSessionProperty(FOLDER_SYNC_KEY, null);
container.setSessionProperty(RESOURCE_SYNC_CACHED_KEY, null);
}
IResource[] members = container.members();
for (int i = 0; i < members.length; i++) {
IResource resource = members[i];
purgeResourceSyncCache(resource);
if (deep && resource.getType() != IResource.FILE) {
purgeCache((IContainer) resource, deep);
}
}
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
/* package*/ void purgeResourceSyncCache(IResource resource) throws CVSException {
try {
resource.setSessionProperty(RESOURCE_SYNC_KEY, null);
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
/**
* Sets the array of folder ignore patterns for the container, must not be null.
* Folder must exist and must not be the workspace root.
*
* @param container the container
* @param ignores the array of ignore patterns
*/
/*package*/ void setCachedFolderIgnores(IContainer container, String[] ignores) throws CVSException {
try {
container.setSessionProperty(IGNORE_SYNC_KEY, ignores);
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
/**
* Sets the folder sync info for the container; if null, deletes it.
* Folder must exist and must not be the workspace root.
* The folder sync info for the container need not have previously been cached.
*
* @param container the container
* @param info the new folder sync info
*/
/*package*/ void setCachedFolderSync(IContainer container, FolderSyncInfo info) throws CVSException {
if (!container.exists()) return;
try {
if (info == null) info = NULL_FOLDER_SYNC_INFO;
container.setSessionProperty(FOLDER_SYNC_KEY, info);
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
/*package*/ void setDirtyIndicator(IResource resource, String indicator) throws CVSException {
if (resource.getType() == IResource.FILE) {
internalSetDirtyIndicator((IFile)resource, indicator);
} else {
internalSetDirtyIndicator((IContainer)resource, indicator);
}
}
/*package*/ String getDirtyIndicator(IResource resource) throws CVSException {
if (resource.getType() == IResource.FILE) {
return internalGetDirtyIndicator((IFile)resource);
} else {
return internalGetDirtyIndicator((IContainer)resource);
}
}
private void internalSetDirtyIndicator(IFile file, String indicator) throws CVSException {
try {
file.setSessionProperty(IS_DIRTY, indicator);
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
private String internalGetDirtyIndicator(IFile file) throws CVSException {
try {
return (String)file.getSessionProperty(IS_DIRTY);
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
private void internalSetDirtyIndicator(IContainer container, String indicator) throws CVSException {
try {
container.setSessionProperty(IS_DIRTY, indicator);
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
private String internalGetDirtyIndicator(IContainer container) throws CVSException {
try {
String di = (String)container.getSessionProperty(IS_DIRTY);
// if the session property is not available then restore from persisted sync info. At this
// time the sync info is not flushed because we don't want the workspace to generate
// a delta. Since the sync info is not flushed another session property is used to remember
// that the sync info was already converted to a session property and has become stale.
if(di == null && container.getSessionProperty(FOLDER_SYNC_RESTORED_KEY) == null) {
byte [] diBytes = ResourcesPlugin.getWorkspace().getSynchronizer().getSyncInfo(RESOURCE_SYNC_KEY, container);
if(diBytes != null) {
di = new String(diBytes);
setDirtyIndicator(container, di);
}
container.setSessionProperty(FOLDER_SYNC_RESTORED_KEY, FOLDER_SYNC_RESTORED);
}
return di;
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
/**
* Return the dirty count for the given folder. For existing folders, the
* dirty count may not have been calculated yet and this method will return
* null in that case. For phantom folders, the dirty count is calculated if
* it does not exist yet.
*/
/*package*/ int getCachedDirtyCount(IContainer container) throws CVSException {
if (!container.exists()) return -1;
try {
Integer dirtyCount = (Integer)container.getSessionProperty(DIRTY_COUNT);
if (dirtyCount == null) {
return -1;
} else {
return dirtyCount.intValue();
}
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
/*package*/ void setCachedDirtyCount(IContainer container, int count) throws CVSException {
if (!container.exists()) return;
try {
container.setSessionProperty(DIRTY_COUNT, new Integer(count));
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
/*
* Flush all cached info for the container and it's ancestors
*/
/*package*/ void flushDirtyCache(IResource resource) throws CVSException {
if (resource.exists()) {
try {
if (resource.getType() == IResource.FILE) {
resource.setSessionProperty(IS_DIRTY, null);
resource.setSessionProperty(CLEAN_UPDATE, null);
} else {
resource.setSessionProperty(DIRTY_COUNT, null);
resource.setSessionProperty(DELETED_CHILDREN, null);
resource.setSessionProperty(IS_DIRTY, null);
}
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
}
/*
* Add the deleted child and return true if it didn't exist before
*/
/*package*/ boolean addDeletedChild(IContainer container, IFile file) throws CVSException {
try {
Set deletedFiles = getDeletedChildren(container);
if (deletedFiles == null)
deletedFiles = new HashSet();
String fileName = file.getName();
if (deletedFiles.contains(fileName))
return false;
deletedFiles.add(fileName);
setDeletedChildren(container, deletedFiles);
return true;
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
/*package*/ boolean removeDeletedChild(IContainer container, IFile file) throws CVSException {
try {
Set deletedFiles = getDeletedChildren(container);
if (deletedFiles == null || deletedFiles.isEmpty())
return false;
String fileName = file.getName();
if (!deletedFiles.contains(fileName))
return false;
deletedFiles.remove(fileName);
if (deletedFiles.isEmpty()) deletedFiles = null;
setDeletedChildren(container, deletedFiles);
return true;
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
protected void setDeletedChildren(IContainer parent, Set deletedFiles) throws CVSException {
if (!parent.exists()) return;
try {
parent.setSessionProperty(DELETED_CHILDREN, deletedFiles);
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
private Set getDeletedChildren(IContainer parent) throws CoreException {
if (!parent.exists()) return null;
return (Set)parent.getSessionProperty(DELETED_CHILDREN);
}
/**
* Method updated flags the objetc as having been modfied by the updated
* handler. This flag is read during the resource delta to determine whether
* the modification made the file dirty or not.
*
* @param mFile
*/
/*package*/ void markFileAsUpdated(IFile file) throws CVSException {
try {
file.setSessionProperty(CLEAN_UPDATE, UPDATED_INDICATOR);
// update dirty state so that decorators don't have to recompute after
// an update is completed.
contentsChangedByUpdate(file);
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
/*package*/ boolean contentsChangedByUpdate(IFile file) throws CVSException {
try {
Object indicator = file.getSessionProperty(CLEAN_UPDATE);
boolean updated = false;
if (indicator == UPDATED_INDICATOR) {
// the file was changed due to a clean update (i.e. no local mods) so skip it
file.setSessionProperty(CLEAN_UPDATE, null);
file.setSessionProperty(IS_DIRTY, NOT_DIRTY_INDICATOR);
updated = true;
}
return updated;
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
/**
* Method isSyncInfoLoaded returns true if all the sync info for the
* provided resources is loaded into the internal cache.
*
* @param resources
* @param i
* @return boolean
*/
/*package*/ boolean isSyncInfoLoaded(IContainer parent) throws CVSException {
try {
if (parent.getFolder(new Path(SyncFileWriter.CVS_DIRNAME)).exists()) {
if (parent.getSessionProperty(RESOURCE_SYNC_CACHED_KEY) == null)
return false;
if (parent.getSessionProperty(FOLDER_SYNC_KEY) == null)
return false;
// if (parent.getSessionProperty(IGNORE_SYNC_KEY) == null)
// return false;
}
} catch (CoreException e) {
// let future operations surface the error
return false;
}
return true;
}
/**
* @see org.eclipse.team.internal.ccvs.core.resources.SyncInfoCache#getCachedSyncBytes(org.eclipse.core.resources.IResource)
*/
/*package*/ byte[] getCachedSyncBytes(IResource resource) throws CVSException {
try {
return (byte[])resource.getSessionProperty(RESOURCE_SYNC_KEY);
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
/**
* @see org.eclipse.team.internal.ccvs.core.resources.SyncInfoCache#setCachedSyncBytes(org.eclipse.core.resources.IResource, byte[])
*/
/*package*/ void setCachedSyncBytes(IResource resource, byte[] syncBytes) throws CVSException {
try {
resource.setSessionProperty(RESOURCE_SYNC_KEY, syncBytes);
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
/* (non-Javadoc)
* @see org.eclipse.team.internal.ccvs.core.resources.SyncInfoCache#isDirtyCacheFlushed(org.eclipse.core.resources.IContainer)
*/
boolean isDirtyCacheFlushed(IContainer resource) throws CVSException {
if (resource.exists()) {
try {
return resource.getSessionProperty(IS_DIRTY) == null;
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
return false;
}
/* (non-Javadoc)
* @see org.eclipse.core.resources.ISaveParticipant#doneSaving(org.eclipse.core.resources.ISaveContext)
*/
public void doneSaving(ISaveContext context) {
}
/* (non-Javadoc)
* @see org.eclipse.core.resources.ISaveParticipant#prepareToSave(org.eclipse.core.resources.ISaveContext)
*/
public void prepareToSave(ISaveContext context) throws CoreException {
}
/* (non-Javadoc)
* @see org.eclipse.core.resources.ISaveParticipant#rollback(org.eclipse.core.resources.ISaveContext)
*/
public void rollback(ISaveContext context) {
}
/* (non-Javadoc)
* @see org.eclipse.core.resources.ISaveParticipant#saving(org.eclipse.core.resources.ISaveContext)
*/
public void saving(ISaveContext context) throws CoreException {
boolean fullSave = (context.getKind() == ISaveContext.FULL_SAVE);
boolean projectSave = (context.getKind() == ISaveContext.PROJECT_SAVE);
if(projectSave || fullSave) {
// persist all session properties for folders into sync info.
final ISynchronizer synchronizer = ResourcesPlugin.getWorkspace().getSynchronizer();
synchronizer.add(RESOURCE_SYNC_KEY);
// traverse the workspace looking for CVS managed projects or just the
// specific projects being closed
IProject[] projects;
if(projectSave) {
projects = new IProject[1];
projects[0] = context.getProject();
} else {
projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
}
for (int i = 0; i < projects.length; i++) {
IProject project = projects[i];
RepositoryProvider provider = RepositoryProvider.getProvider(
project,
CVSProviderPlugin.getTypeId());
// found a project managed by CVS, convert each session property on a
// folder to a sync object.
if (provider != null) {
project.accept(new IResourceVisitor() {
public boolean visit(IResource resource) throws CoreException {
if(resource.getType() != IResource.FILE) {
String di = null;
try {
di = getDirtyIndicator(resource);
} catch (CVSException e) {
// continue traversal
CVSProviderPlugin.log(e);
}
if(di != null) {
synchronizer.setSyncInfo(RESOURCE_SYNC_KEY, resource, di.getBytes());
}
}
return true;
}
});
}
}
}
}
}