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