| package org.eclipse.team.internal.ccvs.core.resources; |
| |
| /* |
| * (c) Copyright IBM Corp. 2000, 2001. |
| * All Rights Reserved. |
| */ |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.DataInputStream; |
| import java.io.DataOutputStream; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.eclipse.core.resources.IContainer; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceVisitor; |
| import org.eclipse.core.resources.ISynchronizer; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.QualifiedName; |
| import org.eclipse.team.ccvs.core.CVSProviderPlugin; |
| import org.eclipse.team.ccvs.core.CVSTag; |
| import org.eclipse.team.ccvs.core.ICVSFile; |
| import org.eclipse.team.ccvs.core.ICVSFolder; |
| import org.eclipse.team.ccvs.core.ICVSResource; |
| import org.eclipse.team.core.TeamPlugin; |
| import org.eclipse.team.core.sync.ISyncProvider; |
| import org.eclipse.team.internal.ccvs.core.CVSException; |
| import org.eclipse.team.internal.ccvs.core.Policy; |
| import org.eclipse.team.internal.ccvs.core.syncinfo.FolderSyncInfo; |
| import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo; |
| import org.eclipse.team.internal.ccvs.core.util.Assert; |
| import org.eclipse.team.internal.ccvs.core.util.SyncFileWriter; |
| |
| /** |
| * A synchronizer is responsible for managing synchronization information for local |
| * CVS resources. |
| * |
| * [Notes: |
| * 1. how can we expire cache elements and purge to safe memory? |
| * 2. how can we safeguard against overwritting meta files changes made outside of Eclipse? I'm |
| * not sure we should force setting file contents in EclipseFile handles? |
| * 4. how do we reload |
| * ] |
| * |
| * @see ResourceSyncInfo |
| * @see FolderSyncInfo |
| */ |
| public class EclipseSynchronizer { |
| // the resources plugin synchronizer is used to cache and possibly persist. These |
| // are keys for storing the sync info. |
| private static final QualifiedName FOLDER_SYNC_KEY = new QualifiedName(CVSProviderPlugin.ID, "folder-sync"); |
| private static final QualifiedName RESOURCE_SYNC_KEY = new QualifiedName(CVSProviderPlugin.ID, "resource-sync"); |
| private static final QualifiedName RESOURCE_SYNC_LOADED_KEY = new QualifiedName(CVSProviderPlugin.ID, "folder-has-loaded-childsync"); |
| private static final QualifiedName IGNORE_SYNC_KEY = new QualifiedName(CVSProviderPlugin.ID, "folder-ignore"); |
| |
| private static final byte[] EMPTY_BYTES = new byte[0]; |
| private static final FolderSyncInfo EMPTY_FOLDER_SYNC_INFO = new FolderSyncInfo("", "", null, false); |
| |
| // the cvs eclipse synchronizer is a singleton |
| private static EclipseSynchronizer instance; |
| |
| // track resources that have changed in a given operation |
| private int nestingCount = 0; |
| private Set changedResources = new HashSet(); |
| private Set changedFolders = new HashSet(); |
| |
| private static final boolean DEBUG = false; |
| |
| private EclipseSynchronizer() { |
| } |
| |
| public static EclipseSynchronizer getInstance() { |
| return instance; |
| } |
| |
| public void setFolderSync(IContainer folder, FolderSyncInfo info) throws CVSException { |
| Assert.isNotNull(info); |
| try { |
| beginOperation(null); |
| setCachedFolderSync(folder, info); |
| changedFolders.add(folder); |
| } finally { |
| endOperation(null); |
| } |
| } |
| |
| public FolderSyncInfo getFolderSync(IContainer folder) throws CVSException { |
| if (folder.getType() == IResource.ROOT) return null; |
| FolderSyncInfo info = getCachedFolderSync(folder); |
| if (info == null && folder.exists()) { |
| // read folder sync info and remember it |
| // -- if none found then remember that fact for later |
| info = SyncFileWriter.readFolderConfig(CVSWorkspaceRoot.getCVSFolderFor(folder)); |
| if (info == null) info = EMPTY_FOLDER_SYNC_INFO; |
| setCachedFolderSync(folder, info); |
| } |
| if (info == EMPTY_FOLDER_SYNC_INFO) info = null; |
| return info; |
| } |
| |
| public void setResourceSync(IResource resource, ResourceSyncInfo info) throws CVSException { |
| Assert.isNotNull(info); |
| try { |
| beginOperation(null); |
| ensureChildResourceSyncLoaded(resource.getParent()); |
| setCachedResourceSync(resource, info); |
| changedResources.add(resource); |
| } finally { |
| endOperation(null); |
| } |
| } |
| |
| public ResourceSyncInfo getResourceSync(IResource resource) throws CVSException { |
| if (resource.getType() == IResource.ROOT) return null; |
| ensureChildResourceSyncLoaded(resource.getParent()); |
| return getCachedResourceSync(resource); |
| } |
| |
| public void deleteFolderSync(IContainer folder, IProgressMonitor monitor) throws CVSException { |
| try { |
| beginOperation(null); |
| FolderSyncInfo info = getCachedFolderSync(folder); |
| if (info != null && info != EMPTY_FOLDER_SYNC_INFO) { |
| // remember that we deleted the folder sync info |
| setCachedFolderSync(folder, EMPTY_FOLDER_SYNC_INFO); |
| changedFolders.add(folder); |
| } |
| } finally { |
| endOperation(null); |
| } |
| } |
| |
| public void deleteResourceSync(IResource resource, IProgressMonitor monitor) throws CVSException { |
| try { |
| beginOperation(null); |
| ensureChildResourceSyncLoaded(resource.getParent()); |
| setCachedResourceSync(resource, null); |
| changedResources.add(resource); |
| } finally { |
| endOperation(null); |
| } |
| } |
| |
| public String[] getIgnored(IResource resource) throws CVSException { |
| IContainer parent = resource.getParent(); |
| if(parent==null || parent.getType()==IResource.ROOT) return null; |
| String[] ignores = getCachedFolderIgnores(parent); |
| if(ignores==null) { |
| ICVSFile ignoreFile = CVSWorkspaceRoot.getCVSFileFor(parent.getFile(new Path(SyncFileWriter.IGNORE_FILE))); |
| if(ignoreFile.exists()) { |
| ignores = SyncFileWriter.readLines(ignoreFile); |
| setCachedFolderIgnores(parent, ignores); |
| } |
| } |
| return ignores; |
| } |
| |
| public void setIgnored(IResource resource, String pattern) throws CVSException { |
| SyncFileWriter.addCvsIgnoreEntry(CVSWorkspaceRoot.getCVSResourceFor(resource), pattern); |
| TeamPlugin.getManager().broadcastResourceStateChanges(new IResource[] {resource}); |
| } |
| |
| public IResource[] members(IContainer folder) throws CVSException { |
| try { |
| // initialize cache if needed, this will create phantoms |
| ensureChildResourceSyncLoaded(folder); |
| IResource[] children = folder.members(true); |
| List list = new ArrayList(children.length); |
| for (int i = 0; i < children.length; ++i) { |
| IResource child = children[i]; |
| if (! child.isPhantom() || getCachedResourceSync(child) != null) { |
| list.add(child); |
| } |
| } |
| return (IResource[]) list.toArray(new IResource[list.size()]); |
| } catch (CoreException e) { |
| throw CVSException.wrapException(e); |
| } |
| } |
| |
| static public void startup() { |
| Assert.isTrue(instance==null); |
| instance = new EclipseSynchronizer(); |
| getSynchronizer().add(RESOURCE_SYNC_KEY); |
| getSynchronizer().add(RESOURCE_SYNC_LOADED_KEY); |
| getSynchronizer().add(FOLDER_SYNC_KEY); |
| getSynchronizer().add(IGNORE_SYNC_KEY); |
| try { |
| flushAll(ResourcesPlugin.getWorkspace().getRoot(), false /*don't purge from disk*/); |
| } catch(CVSException e) { |
| // // severe problem, it would mean that we are working with stale sync info |
| CVSProviderPlugin.log(e.getStatus()); |
| } |
| } |
| |
| static public void shutdown() { |
| // so that the workspace won't persist cached sync info |
| getSynchronizer().remove(RESOURCE_SYNC_KEY); |
| getSynchronizer().remove(RESOURCE_SYNC_LOADED_KEY); |
| getSynchronizer().remove(FOLDER_SYNC_KEY); |
| getSynchronizer().remove(IGNORE_SYNC_KEY); |
| } |
| |
| public void beginOperation(IProgressMonitor monitor) throws CVSException { |
| if (nestingCount++ == 0) { |
| // any work here? |
| |
| // uncomment this line to bypass cache for testing |
| //flushAll(ResourcesPlugin.getWorkspace().getRoot(), false /*don't purge from disk*/); |
| } |
| } |
| |
| public void endOperation(IProgressMonitor monitor) throws CVSException { |
| if (--nestingCount == 0) { |
| if (! changedFolders.isEmpty() || ! changedResources.isEmpty()) { |
| try { |
| monitor = Policy.monitorFor(monitor); |
| int numResources = changedFolders.size() + changedResources.size(); |
| monitor.beginTask(null, numResources); |
| monitor.subTask("Updating CVS synchronization information..."); |
| /* write sync info to disk */ |
| List deletedFolderSync = new ArrayList(); |
| |
| // folder sync info changes |
| Iterator it = changedFolders.iterator(); |
| while (it.hasNext()) { |
| IContainer folder = (IContainer) it.next(); |
| FolderSyncInfo info = getCachedFolderSync(folder); |
| ICVSFolder cvsFolder = CVSWorkspaceRoot.getCVSFolderFor(folder); |
| if (info == EMPTY_FOLDER_SYNC_INFO) { |
| // deleted folder sync info since we loaded it, postpone change until later |
| deletedFolderSync.add(folder); |
| } else if (info == null) { |
| // attempted to delete folder sync info for a previously unmanaged folder |
| // no-op |
| } else { |
| // modified or created new folder sync info since we loaded it |
| SyncFileWriter.writeFolderConfig(cvsFolder, info); |
| } |
| monitor.worked(1); |
| } |
| |
| // resource sync info changes |
| it = changedResources.iterator(); |
| while (it.hasNext()) { |
| IResource resource = (IResource) it.next(); |
| ResourceSyncInfo info = getCachedResourceSync(resource); |
| ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resource); |
| if(!isChildResourceSyncLoaded(resource.getParent())) { |
| return; |
| } |
| Assert.isTrue(isChildResourceSyncLoaded(resource.getParent())); |
| if (info == null) { |
| // deleted resource sync info since we loaded it |
| SyncFileWriter.deleteSync(cvsResource); |
| } else { |
| // modified or created new resource sync info since we loaded it |
| SyncFileWriter.writeResourceSync(cvsResource, info); |
| } |
| monitor.worked(1); |
| } |
| |
| // folder sync info deletes |
| it = deletedFolderSync.iterator(); |
| while (it.hasNext()) { |
| IContainer folder = (IContainer) it.next(); |
| // flush everything that was cached from the CVS subdirectory |
| flushAll(folder, true /*purge from disk*/); |
| } |
| |
| // broadcast events |
| changedResources.addAll(changedFolders); |
| IResource[] resources = (IResource[]) changedResources.toArray( |
| new IResource[changedResources.size()]); |
| TeamPlugin.getManager().broadcastResourceStateChanges(resources); |
| changedResources.clear(); |
| changedFolders.clear(); |
| } finally { |
| monitor.done(); |
| } |
| } |
| } |
| Assert.isTrue(nestingCount>= 0); |
| } |
| |
| private static ISynchronizer getSynchronizer() { |
| return ResourcesPlugin.getWorkspace().getSynchronizer(); |
| } |
| |
| /* |
| * Returns the cached resource sync info, or null if none found. |
| */ |
| private ResourceSyncInfo getCachedResourceSync(IResource resource) throws CVSException { |
| try { |
| byte[] bytes = getSynchronizer().getSyncInfo(RESOURCE_SYNC_KEY, resource); |
| if(bytes == null) return null; |
| return new ResourceSyncInfo(new String(bytes), null, null); |
| } catch(CoreException e) { |
| throw CVSException.wrapException(e); |
| } |
| } |
| |
| /* |
| * Sets the cached resource sync info, use null to delete it. |
| */ |
| private void setCachedResourceSync(IResource resource, ResourceSyncInfo info) throws CVSException { |
| try { |
| if(info==null) { |
| getSynchronizer().setSyncInfo(RESOURCE_SYNC_KEY, resource, null); // faster than flush |
| } else { |
| getSynchronizer().setSyncInfo(RESOURCE_SYNC_KEY, resource, info.getEntryLine(true).getBytes()); |
| } |
| } catch(CoreException e) { |
| throw CVSException.wrapException(e); |
| } |
| } |
| |
| /* |
| * Returns the cached sync info for a folder, null if none found, or |
| * special placeholder EMPTY_FOLDER_SYNC_INFO for deleted sync info. |
| */ |
| private FolderSyncInfo getCachedFolderSync(IContainer folder) throws CVSException { |
| try { |
| byte[] bytes = getSynchronizer().getSyncInfo(FOLDER_SYNC_KEY, folder); |
| if (bytes == null) return null; |
| if (bytes.length == 0) return EMPTY_FOLDER_SYNC_INFO; // return placeholder for deleted sync info |
| DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes)); |
| String repo = is.readUTF(); |
| String root = is.readUTF(); |
| String tag = is.readUTF(); |
| CVSTag cvsTag = null; |
| boolean isStatic = is.readBoolean(); |
| if(!tag.equals("null")) { |
| cvsTag = new CVSEntryLineTag(tag); |
| } |
| return new FolderSyncInfo(repo, root, cvsTag, isStatic); |
| } catch (CoreException e) { |
| throw CVSException.wrapException(e); |
| } catch(IOException e) { |
| throw CVSException.wrapException(e); |
| } |
| } |
| |
| /* |
| * Sets the cached sync info for a folder, use null to flush, or special |
| * EMPTY_FOLDER_SYNC_INFO placeholder for deleted sync info. |
| */ |
| private void setCachedFolderSync(IContainer folder, FolderSyncInfo info) throws CVSException { |
| try { |
| if (info == null) { |
| getSynchronizer().setSyncInfo(FOLDER_SYNC_KEY, folder, null); // faster than flush |
| } else if (info == EMPTY_FOLDER_SYNC_INFO ) { |
| // memorize placeholder for deleted sync info |
| getSynchronizer().setSyncInfo(FOLDER_SYNC_KEY, folder, EMPTY_BYTES); |
| } else { |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
| DataOutputStream os = new DataOutputStream(bos); |
| os.writeUTF(info.getRepository()); |
| os.writeUTF(info.getRoot()); |
| CVSEntryLineTag tag = info.getTag(); |
| if(tag==null) { |
| os.writeUTF("null"); |
| } else { |
| os.writeUTF(info.getTag().toEntryLineFormat(false)); |
| } |
| os.writeBoolean(info.getIsStatic()); |
| getSynchronizer().setSyncInfo(FOLDER_SYNC_KEY, folder, bos.toByteArray()); |
| os.close(); |
| } |
| } catch (CoreException e) { |
| throw CVSException.wrapException(e); |
| } catch(IOException e) { |
| throw CVSException.wrapException(e); |
| } |
| } |
| |
| private String[] getCachedFolderIgnores(IContainer folder) throws CVSException { |
| try { |
| byte[] bytes = getSynchronizer().getSyncInfo(IGNORE_SYNC_KEY, folder); |
| if (bytes == null) return null; |
| DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes)); |
| int count = is.readInt(); |
| String[] ignoreList = new String[count]; |
| for(int i = 0; i < count; ++i) { |
| ignoreList[i] = is.readUTF(); |
| } |
| return ignoreList; |
| } catch (CoreException e) { |
| throw CVSException.wrapException(e); |
| } catch(IOException e) { |
| throw CVSException.wrapException(e); |
| } |
| } |
| |
| private void setCachedFolderIgnores(IContainer folder, String[] ignores) throws CVSException { |
| try { |
| if (ignores == null) { |
| getSynchronizer().setSyncInfo(IGNORE_SYNC_KEY, folder, null); // faster than flush |
| } else { |
| // a zero-length array indicates there were no ignores found |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
| DataOutputStream os = new DataOutputStream(bos); |
| os.writeInt(ignores.length); |
| for(int i = 0; i < ignores.length; ++i) { |
| os.writeUTF(ignores[i]); |
| } |
| getSynchronizer().setSyncInfo(IGNORE_SYNC_KEY, folder, bos.toByteArray()); |
| os.close(); |
| } |
| } catch (CoreException e) { |
| throw CVSException.wrapException(e); |
| } catch(IOException e) { |
| throw CVSException.wrapException(e); |
| } |
| } |
| |
| /* |
| * Reads and caches the ResourceSyncInfos for this folder if not already cached. |
| */ |
| private void ensureChildResourceSyncLoaded(IContainer folder) throws CVSException { |
| // don't try to load if the information is already cached |
| if (isChildResourceSyncLoaded(folder)) return; |
| ResourceSyncInfo[] infos = SyncFileWriter.readEntriesFile(CVSWorkspaceRoot.getCVSFolderFor(folder)); |
| if (infos != null) { |
| for (int i = 0; i < infos.length; i++) { |
| ResourceSyncInfo syncInfo = infos[i]; |
| IResource peer; |
| IPath path = new Path(syncInfo.getName()); |
| if (syncInfo.isDirectory()) { |
| peer = folder.getFolder(path); |
| } else { |
| peer = folder.getFile(path); |
| } |
| // may create a phantom if the sibling resource does not exist. |
| setCachedResourceSync(peer, syncInfo); |
| } |
| } |
| setChildResourceSyncLoaded(folder, true); |
| } |
| |
| private boolean isChildResourceSyncLoaded(IContainer folder) throws CVSException { |
| try { |
| // root folder has no entries therefore info is always loaded |
| if (folder.getType() == IResource.ROOT || ! folder.exists()) return true; |
| return getSynchronizer().getSyncInfo(RESOURCE_SYNC_LOADED_KEY, folder) != null; |
| } catch(CoreException e) { |
| throw CVSException.wrapException(e); |
| } |
| } |
| |
| private void setChildResourceSyncLoaded(IContainer folder, boolean isLoaded) throws CVSException { |
| try { |
| getSynchronizer().setSyncInfo(RESOURCE_SYNC_LOADED_KEY, folder, isLoaded ? EMPTY_BYTES : null); |
| } catch(CoreException e) { |
| throw CVSException.wrapException(e); |
| } |
| |
| } |
| |
| private void flushChildResourceSync(IContainer folder, int depth) throws CVSException { |
| // flushSyncInfo fails with an exception if the folder does not exist |
| if (! folder.exists()) return; |
| try { |
| byte[] folderSyncBytes = getSynchronizer().getSyncInfo(RESOURCE_SYNC_KEY, folder); |
| getSynchronizer().flushSyncInfo(RESOURCE_SYNC_KEY, folder, depth); |
| getSynchronizer().setSyncInfo(RESOURCE_SYNC_KEY, folder, folderSyncBytes); |
| } catch(CoreException e) { |
| throw CVSException.wrapException(folder, "Error flushing a folder's children sync", e); |
| } |
| } |
| |
| static private void flushAll(final IContainer root, final boolean purgeFromDisk) throws CVSException { |
| if (! (root.exists() || root.isPhantom())) return; |
| try { |
| // purge sync information from children |
| root.accept(new IResourceVisitor() { |
| public boolean visit(IResource resource) throws CoreException { |
| if(! root.equals(resource)) { |
| // don't clear resource sync on root since it might still be managed |
| // by its parent |
| getSynchronizer().setSyncInfo(RESOURCE_SYNC_KEY, resource, null); |
| } |
| if(resource.getType()!=IResource.FILE) { |
| IContainer folder = (IContainer) resource; |
| getSynchronizer().setSyncInfo(RESOURCE_SYNC_LOADED_KEY, folder, null); |
| getSynchronizer().setSyncInfo(FOLDER_SYNC_KEY, folder, null); |
| getSynchronizer().setSyncInfo(IGNORE_SYNC_KEY, folder, null); |
| if(purgeFromDisk) { |
| SyncFileWriter.deleteCVSSubDirectory(folder); |
| } |
| } |
| return true; |
| } |
| |
| }, IResource.DEPTH_INFINITE, true /*include phantoms*/); |
| } catch(CoreException e) { |
| throw CVSException.wrapException(root, "Problems occured flushing synchronizer cache", e); |
| } |
| } |
| } |