| /******************************************************************************* |
| * 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.core.target; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.DataInputStream; |
| import java.io.DataOutputStream; |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.URL; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.core.resources.IContainer; |
| import org.eclipse.core.resources.IFolder; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.QualifiedName; |
| import org.eclipse.team.core.TeamException; |
| import org.eclipse.team.internal.core.Assert; |
| import org.eclipse.team.internal.core.NullSubProgressMonitor; |
| import org.eclipse.team.internal.core.Policy; |
| import org.eclipse.team.internal.core.TeamPlugin; |
| |
| /** |
| * This abstract class implements the state of a local and corresponding remote resource, |
| * and behavior of those resources. |
| * <p> |
| * Common state for all 'managed' resources includes: |
| * <ul> |
| * <li>the local resource and corresponding remote resource objects</li> |
| * <li>the timestamp of the local resource as it was last in synch with the provider</li> |
| * <li>an identifier for the remote resource as it was last in synch with the provider</li> |
| */ |
| public abstract class ResourceState { |
| |
| /* |
| * Serialization format identifier. |
| * see toBytes() and fromBytes() |
| */ |
| private static final int BYTES_FORMAT = 0; |
| |
| /* |
| * These constants are used to indicate uninitialized values for the local |
| * base timestamp and remote base identifier. |
| */ |
| protected static final long EMPTY_LOCALBASETS = -1L; |
| protected static final String EMPTY_REMOTEBASEID = "Undefined:"; //$NON-NLS-1$ |
| |
| /* |
| * The base state of the resource. The 'base' is the state of the resource |
| * state that was fetched from (or put in) the provider. |
| */ |
| protected long localBaseTimestamp = EMPTY_LOCALBASETS; |
| protected String remoteBaseIdentifier = EMPTY_REMOTEBASEID; |
| |
| protected boolean checkedOut = true; |
| |
| /* |
| * This is the local resource that the receiver represents. It is initialized by |
| * the constructor. (The remote resource is maintained by specific subclasses |
| * as it is type-dependent.) |
| */ |
| protected IResource localResource; |
| |
| protected QualifiedName stateKey = new QualifiedName("org.eclipse.team.target", "state_info"); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| protected URL rootUrl; |
| |
| /** |
| * Constructor for a resource state given a local resource. |
| * Remember which local resource this state represents. |
| * |
| * @param localResource the local part of a synchronized pair of resources. |
| */ |
| public ResourceState(IResource localResource, URL rootUrl) { |
| super(); |
| this.rootUrl = rootUrl; |
| SynchronizedTargetProvider.getSynchronizer().add(stateKey); |
| this.localResource = localResource; |
| } |
| |
| /** |
| * Get the timestamp that represents the base state of the local resource, that is |
| * the state that the local resource had when it was initially fetched from the repository. |
| * |
| * @return the timestamp of the local state of the resource (as reported by |
| * java.io.File.getLastModified()) at the point the resource was downloaded to the |
| * workspace. |
| * @throws BaseIdentifierNotInitializedException if the resource has not yet been |
| * downloaded. |
| */ |
| public long getLocalBaseTimestamp() |
| throws BaseIdentifierNotInitializedException { |
| if (localBaseTimestamp == EMPTY_LOCALBASETS) |
| throw new BaseIdentifierNotInitializedException(); |
| return localBaseTimestamp; |
| } |
| |
| |
| /** |
| * Get the identifier that represents the base state of the remote resource, that is |
| * the state of the remote resource when it was fetched as the base state of the |
| * local resource. |
| * <p> |
| * In general, repositories have arbitrary ways to distinguish resource states. |
| * The result should only be used for equality comparison, there should be no |
| * ordering or other information implied from the value returned. For example, |
| * the value may be a version identifier, timestamp, ETag, etc. To ensure |
| * schemes do not inadvertantly test equal it is recommended that the identifier |
| * be a URI where the scheme denotes the value type, |
| * e.g., date-rfc1123:Fri, 16 Nov 2001 06:25:24 GMT</p> |
| * |
| * @return an opaque identifier to the base state of the resource in the provider. |
| * @throws BaseIdentifierNotInitializedException if the resource has not yet been |
| * downloaded. |
| */ |
| public String getRemoteBaseIdentifier() |
| throws BaseIdentifierNotInitializedException { |
| if (remoteBaseIdentifier.equals(EMPTY_REMOTEBASEID)) |
| throw new BaseIdentifierNotInitializedException(); |
| return remoteBaseIdentifier; |
| } |
| |
| |
| /** |
| * Get the identifier that represents the released state of the resource, |
| * that is the state that it currently has in the repository. |
| * <p> |
| * In general, repositories have arbitrary ways to distinguish resource states. |
| * The result should only be used for equality comparison, there should be no |
| * ordering or other information implied from the value returned. For example, |
| * the value may be a version identifier, timestamp, ETag, etc. To ensure |
| * schemes do not inadvertantly test equal it is recommended that the identifier |
| * be a URI where the scheme denotes the value type, |
| * e.g., date-rfc1123:Fri, 16 Nov 2001 06:25:24 GMT</p> |
| * |
| * @param progress a progress monitor to indicate the duration of the operation, or |
| * <code>null</code> if progress reporting is not required. |
| * @return an opaque identifier to the current released state of the resource in |
| * the provider. |
| * @throws TeamException if there is a problem getting the released state |
| * identifier from the provider. Valid exception status codes include: |
| * <ul> |
| * <li>IO_FAILED</li> |
| * <li>NO_REMOTE_RESOURCE</li> |
| * </ul></p> |
| */ |
| public abstract String getReleasedIdentifier(IProgressMonitor monitor) throws TeamException; |
| |
| /** |
| * Check out the receiver. Return a status if the receiver is in the wrong state for the operation to be performed. |
| * |
| * @throws TeamException if there is a error communicating with the resource from the server. |
| */ |
| public void checkout(IProgressMonitor progress) throws TeamException { |
| progress.beginTask(null, 100); |
| try { |
| // Not going to allow branching. |
| if (isOutOfDate(Policy.subMonitorFor(progress, 50))) |
| throw new TeamException(ITeamStatusConstants.CONFLICT_STATUS); |
| |
| // Sanity check. |
| if (!hasRemote(Policy.subMonitorFor(progress, 50))) |
| throw new TeamException(ITeamStatusConstants.NO_REMOTE_RESOURCE_STATUS); |
| |
| // Legally, the resource must be checked in before it can be checked out. |
| if (isCheckedOut()) |
| throw new TeamException(ITeamStatusConstants.NOT_CHECKED_IN_STATUS); |
| |
| // Do the provider specific action for check-out. |
| basicCheckout(progress); |
| } finally { |
| progress.done(); |
| } |
| } |
| |
| /** |
| * A basic checkout is provider specific. |
| * Unless overridden, work in an optimistic mode. |
| */ |
| protected void basicCheckout(IProgressMonitor progress) throws TeamException { |
| checkedOut = true; |
| } |
| |
| |
| /** |
| * Check in the receiver. |
| * |
| * @throws TeamException if there is a error communicating with the resource from the server. |
| */ |
| public void checkin(IProgressMonitor progress) throws TeamException { |
| progress = Policy.monitorFor(progress); |
| progress.beginTask(null, 100); |
| try { |
| // The resource must be checked out before it can be checked in. |
| if (!isCheckedOut()) |
| throw new TeamException(ITeamStatusConstants.NOT_CHECKED_OUT_STATUS); |
| |
| if (!hasLocal()) { |
| if (hasRemote(Policy.subMonitorFor(progress, 10))) { |
| delete(Policy.subMonitorFor(progress, 80)); |
| } |
| } else { |
| // Ensure the necessary remote direcotories exist |
| mkRemoteDirs(Policy.subMonitorFor(progress, 10)); |
| // Copy from the local resource to the repository. |
| if (getLocal().getType() == IResource.FILE) { |
| upload(Policy.subMonitorFor(progress, 80)); |
| } |
| } |
| //if we got to here the upload succeeded (didn't throw) |
| checkedOut = false; |
| } finally { |
| progress.done(); |
| } |
| } |
| |
| /** |
| * Uncheckout the receiver. |
| */ |
| public void uncheckout(IProgressMonitor progress) throws TeamException { |
| // Has to be checked-out before it can be reversed. |
| if (!isCheckedOut()) |
| throw new TeamException(ITeamStatusConstants.NOT_CHECKED_OUT_STATUS); |
| |
| // Nothing interesting to do since the API spec. requires that we do not reverse |
| // any local changes. |
| checkedOut = false; |
| } |
| |
| /** |
| * Answer whether the receiver is checked out or not. |
| * <p> |
| * Note that this is a quick operation that will be called from the UI, so providers are required |
| * to cache information that is expensive to compute. Where the cache may get stale users |
| * have the opportunity to force a refresh using ITeamProvider.refreshState(). |
| * |
| * @return <code>true</code> if the receiver is checked in, and <code>false</code> |
| * if it is not. |
| * @see ITeamProvider#isCheckedOut(IResource) |
| * @see ITeamProvider#refreshState(IResource[], int, IProgressMonitor) |
| */ |
| public boolean isCheckedOut() { |
| return checkedOut; |
| } |
| |
| /** |
| * Answer if the local resource currently has a different timestamp to the |
| * base timestamp for this resource. |
| * |
| * @return <code>true</code> if the resource has a different modification |
| * timestamp, and <code>false</code> otherwise. |
| * @see ITeamProvider#isDirty(IResource) |
| */ |
| public boolean isDirty() { |
| if (!hasPhantom()) return false; |
| if (!hasLocal()) return true; |
| if (localBaseTimestamp == EMPTY_LOCALBASETS) |
| return localResource.getType() == IResource.FILE; |
| return localBaseTimestamp != localResource.getModificationStamp(); |
| } |
| |
| /** |
| * Answers true if the base identifier of the given resource is different to the |
| * current released state of the resource. |
| */ |
| public boolean isOutOfDate(IProgressMonitor monitor) throws TeamException { |
| if (!hasPhantom()) return false; |
| if (remoteBaseIdentifier.equals(EMPTY_REMOTEBASEID)) return false; |
| String releasedIdentifier = getReleasedIdentifier(monitor); |
| return !remoteBaseIdentifier.equals(releasedIdentifier); |
| } |
| |
| /** |
| * Download the remote resource represented by the receiver state to the location |
| * represented by the local resource (i.e., resource.getLocation().toFile()). |
| * This copies from the provider to the workspace, <em>and</em> sets the local |
| * base timestamp and remote base identifier. |
| * The provider may (and should wherever possible) optimize the case where it |
| * knows the local resource is identical to the remote resource. |
| */ |
| public abstract void download(IProgressMonitor progress) throws TeamException; |
| |
| /** |
| * Upload the resource represented by the local resource to the remote |
| * resource represented by the receiver. This copies from the workspace to |
| * the provider <em>and</em> sets the local base timestamp and remote base |
| * identifier. |
| */ |
| public abstract void upload(IProgressMonitor progress) throws TeamException; |
| |
| /** |
| * Delete the remote resource. |
| */ |
| public abstract void delete(IProgressMonitor progress) throws TeamException; |
| |
| /** |
| * Answer if the remote resource exists. |
| */ |
| public abstract boolean hasRemote(IProgressMonitor monitor) throws TeamException; |
| |
| /** |
| * Answer the type of the remote resource (if it exists). |
| * The type should correspond to the IResource enumerated types. |
| */ |
| public abstract int getRemoteType(); |
| |
| /** |
| * Answer the array of resource states for each member of the receiver. |
| * If the receiver has no members (or is incapable of having members) |
| * answer an empty array. |
| */ |
| public abstract ResourceState[] getRemoteChildren(IProgressMonitor monitor) throws TeamException; |
| |
| /** |
| * Create the necessary remote directories corresponding to the local resource. |
| * That is, if the resource is a folder, create it and its parents if they don't |
| * already exist. If the resource is a file, create its parents if they don't |
| * already exist. |
| */ |
| protected abstract void mkRemoteDirs(IProgressMonitor monitor) throws TeamException; |
| |
| /** |
| */ |
| public IResource getLocal() { |
| return localResource; |
| } |
| |
| /** |
| * Get the file underlying the local resource. |
| */ |
| protected File getLocalFile() { |
| return localResource.getLocation().toFile(); |
| } |
| |
| /** |
| * Answer if the local resource exists. |
| */ |
| protected boolean hasLocal() { |
| return localResource.exists(); |
| } |
| |
| /** |
| * Answer if the local resource has a phantom, which indicates that the respource had both a local |
| * and remote version at one time. |
| */ |
| protected boolean hasPhantom() { |
| try { |
| return SynchronizedTargetProvider.getSynchronizer().getSyncInfo(stateKey, localResource) != null; |
| } catch (CoreException e) { |
| TeamPlugin.log(e.getStatus()); |
| return false; |
| } |
| } |
| |
| /** |
| * Initializes the resource state instance from the given serialized state. |
| * The format of the serialized state is that produced by <code>toBytes()</code>. |
| * |
| * @param bytes the serialized resource state. |
| */ |
| public final void loadState() throws TeamException { |
| try { |
| byte[] storedState = |
| SynchronizedTargetProvider.getSynchronizer().getSyncInfo(stateKey, localResource); |
| if (storedState != null) |
| fromBytes(storedState); |
| } catch (CoreException e) { |
| throw TeamPlugin.wrapException(e); |
| } |
| } |
| |
| /** |
| * Initializes the resource state instance from the given serialized state. |
| * The format of the serialized state is that produced by <code>toBytes()</code>. |
| * |
| * @param bytes the serialized resource state. |
| */ |
| protected void fromBytes(byte[] bytes) throws TeamException{ |
| try { |
| DataInputStream dataStream = |
| new DataInputStream(new ByteArrayInputStream(bytes)); |
| if (BYTES_FORMAT != dataStream.readByte()) |
| return; |
| |
| // Restore common resource state values. |
| remoteBaseIdentifier = dataStream.readUTF(); |
| localBaseTimestamp = dataStream.readLong(); |
| |
| } catch (IOException e) { |
| throw TeamPlugin.wrapException(e); |
| } |
| }; |
| |
| public final void storeState() throws TeamException { |
| try { |
| SynchronizedTargetProvider.getSynchronizer().setSyncInfo( |
| stateKey, |
| localResource, |
| toBytes()); |
| // Ensure that the parent has base info recorded (otherwise deleting the parent will cause the lose of sync info) |
| if (localResource.getType() == IResource.PROJECT) return; |
| IContainer parent = localResource.getParent(); |
| if (parent != null && parent.getType() != IResource.PROJECT && |
| SynchronizedTargetProvider.getSynchronizer().getSyncInfo(stateKey, parent) == null) { |
| getParent().storeState(); |
| } else { |
| ResourcesPlugin.getWorkspace().save(false, null); |
| } |
| } catch (CoreException e) { |
| throw TeamPlugin.wrapException(e); |
| } |
| } |
| |
| /** |
| * Answer the resource state as a sequence of bytes, in a format that can be used to |
| * reconstruct an equivalent resource state using the <code>fromBytes(byte[])</code> |
| * method. |
| * <p> |
| * Subclasses should implement <code>storeState(DataOutputStream)</code> to |
| * store provider specific state information.</p> |
| * |
| * @return the resource state as a byte array. |
| * @see #storeState(DataOutputStream) |
| * @see fromBytes(byte[]) |
| */ |
| protected byte[] toBytes() throws TeamException { |
| try { |
| // Create a stream to store the byte representation of the receiver's state. |
| ByteArrayOutputStream byteStream = new ByteArrayOutputStream(32); |
| // Guess ~32 bytes |
| DataOutputStream dataStream = new DataOutputStream(byteStream); |
| dataStream.writeByte(BYTES_FORMAT); |
| |
| // Store data common to all resource states. |
| dataStream.writeUTF(remoteBaseIdentifier); |
| dataStream.writeLong(localBaseTimestamp); |
| |
| dataStream.close(); |
| return byteStream.toByteArray(); |
| } catch (IOException e) { |
| throw TeamPlugin.wrapException(e); |
| } |
| } |
| |
| final public void removeState() throws TeamException { |
| try { |
| if (localResource.exists() || localResource.isPhantom()) { |
| SynchronizedTargetProvider.getSynchronizer().flushSyncInfo( |
| stateKey, |
| localResource, |
| IResource.DEPTH_INFINITE); |
| } |
| } catch (CoreException e) { |
| throw TeamPlugin.wrapException(e); |
| } |
| } |
| |
| /** |
| * @see org.eclipse.core.runtime.IAdaptable#getAdapter(Class) |
| */ |
| public Object getAdapter(Class adapter) { |
| return Platform.getAdapterManager().getAdapter(this, adapter); |
| } |
| |
| /** |
| * Method getRoot. |
| * @return URL of this resource's parent |
| */ |
| public URL getRoot() { |
| return rootUrl; |
| } |
| |
| private ResourceState getParent() throws TeamException { |
| return getResourceStateFor(localResource.getParent()); |
| } |
| |
| private ResourceState getResourceStateFor(IResource resource) throws TeamException { |
| TargetProvider provider = TargetManager.getProvider(resource.getProject()); |
| return ((SynchronizedTargetProvider)provider).getState(resource); |
| } |
| |
| /** |
| * Get the resource corresponding to the receiver to the specified depth. |
| */ |
| protected final void get(int depth, IProgressMonitor progress) throws TeamException { |
| |
| progress = Policy.monitorFor(progress); |
| |
| // the no progress monitor is used to control the progress given to |
| // other methods and ensures that the progress monitor is not overloaded |
| // with subtask messages and work. The null monitor does propagate |
| // cancellation. |
| IProgressMonitor noProgress = new NullSubProgressMonitor(progress); |
| try { |
| progress.beginTask(null, 100); |
| Policy.checkCanceled(progress); |
| |
| // If remote does not exist then simply ensure no local resource exists. |
| if (!hasRemote(noProgress)) { |
| if (hasLocal()) |
| deleteLocal(noProgress); |
| return; |
| } |
| |
| // Ensure that the required local folders exist |
| if (!hasLocal()) { |
| mkLocalDirs(noProgress); |
| } |
| |
| // If the remote resource is a file, download the remote contents |
| if (getRemoteType() == IResource.FILE) { |
| download(Policy.subMonitorFor(progress, 100)); |
| return; |
| } |
| |
| // The remote resource is a container. |
| |
| // If the local resource is a file, we must remove it first. |
| if (getLocal().getType() == IResource.FILE) { |
| if (hasLocal()) { |
| deleteLocal(noProgress); // May not exist. |
| } |
| // change the local resource to a folder and create it |
| localResource = localResource.getParent().getFolder(new Path(localResource.getName())); |
| mkLocalDirs(noProgress); |
| } |
| |
| // Finally, resolve the collection membership based upon the depth parameter. |
| switch (depth) { |
| case IResource.DEPTH_ZERO : |
| // If we are not considering members of the collection then we are done. |
| return; |
| case IResource.DEPTH_ONE : |
| // If we are considering only the immediate members of the collection |
| getFolderShallow(Policy.subMonitorFor(progress, 100)); |
| return; |
| case IResource.DEPTH_INFINITE : |
| // We are going in deep. |
| getFolderDeep(Policy.subMonitorFor(progress, 100)); |
| return; |
| default : |
| // We have covered all the legal cases. |
| Assert.isLegal(false); |
| return; // Never reached. |
| } // end switch |
| } finally { |
| progress.done(); |
| } |
| } |
| |
| /** |
| * Get the folder resource represented by the receiver deeply. |
| */ |
| protected final void getFolderDeep(IProgressMonitor progress) throws TeamException { |
| progress = Policy.monitorFor(progress); |
| try { |
| progress.beginTask(null, 10); |
| // Could throw if problem getting the folder at this level. |
| ResourceState[] childFolders = getFolderShallow(Policy.subMonitorFor(progress, 7)); |
| |
| // If there are no further children then we are done. |
| if (childFolders.length == 0) |
| return; |
| |
| IProgressMonitor subProgress = Policy.subMonitorFor(progress, 3); |
| // Collect the responses in the multistatus. |
| try { |
| subProgress.beginTask(null, childFolders.length); |
| for (int i = 0; i < childFolders.length; i++) { |
| childFolders[i].get(IResource.DEPTH_INFINITE, Policy.subMonitorFor(subProgress, 1)); |
| } |
| } finally { |
| subProgress.done(); |
| } |
| |
| return; |
| } finally { |
| progress.done(); |
| } |
| } |
| |
| /** |
| * Synchronize from the remote provider to the workspace. |
| * Assume that the 'remote' folder is correct, and change the local |
| * folder to look like the remote folder. |
| * |
| * returns an array of children of the remote resource that are themselves |
| * collections. |
| */ |
| protected final ResourceState[] getFolderShallow(IProgressMonitor progress) throws TeamException { |
| progress = Policy.monitorFor(progress); |
| IProgressMonitor noProgress = new NullProgressMonitor(); |
| try { |
| // We are assuming that the resource is a container. |
| Assert.isLegal(getLocal() instanceof IContainer); |
| IContainer localContainer = (IContainer)getLocal(); |
| |
| // Get list of all _remote_ children. |
| ResourceState[] remoteChildren = getRemoteChildren(noProgress); |
| |
| // This will be the list of remote children that are themselves containers. |
| Set remoteChildFolders = new HashSet(); |
| |
| // Make a list of _local_ children that have not yet been processed, |
| IResource[] localChildren = getLocalChildren(); |
| Set surplusLocalChildren = new HashSet(localChildren.length); |
| surplusLocalChildren.addAll(Arrays.asList(localChildren)); |
| |
| progress.beginTask(null, remoteChildren.length * 100); |
| // For each remote child that is a file, make the local file content equivalent. |
| for (int i = 0; i < remoteChildren.length; i++) { |
| Policy.checkCanceled(progress); |
| ResourceState remoteChildState = remoteChildren[i]; |
| // If the remote child is a container add it to the list, and ensure that the local child |
| // is a folder if it exists. |
| if (remoteChildState.getRemoteType() == IResource.FILE) { |
| // The remote resource is a file. Copy the content of the remote file |
| // to the local file, overwriting any existing content that may exist, and |
| // creating the file if it doesn't. |
| remoteChildState.download(Policy.subMonitorFor(progress, 100)); |
| // Remember that we have processed this child. |
| surplusLocalChildren.remove(remoteChildState.getLocal()); |
| } else { |
| // The remote resource is a container. |
| remoteChildFolders.add(remoteChildState); |
| // If the local child is not a container then it must be deleted. |
| IResource localChild = remoteChildState.getLocal(); |
| if (localChild.exists() && (!(localChild instanceof IContainer))) |
| remoteChildState.deleteLocal(noProgress); |
| } // end if |
| } // end for |
| |
| // Remove each local child that does not have a corresponding remote resource. |
| TargetProvider provider = TargetManager.getProvider(localContainer.getProject()); |
| Iterator childrenItr = surplusLocalChildren.iterator(); |
| while (childrenItr.hasNext()) { |
| IResource unseenChild = (IResource) childrenItr.next(); |
| ((SynchronizedTargetProvider)provider).newState(unseenChild).deleteLocal(noProgress); |
| } // end-while |
| |
| // Answer the array of children seen on the remote collection that are |
| // themselves collections (to support depth operations). |
| return (ResourceState[]) remoteChildFolders.toArray( |
| new ResourceState[remoteChildFolders.size()]); |
| } finally { |
| progress.done(); |
| } |
| } |
| |
| /** |
| * Delete the local resource represented by the resource state. Do not complain if the resource does not exist. |
| */ |
| protected final void deleteLocal(IProgressMonitor progress) throws TeamException { |
| try { |
| getLocal().delete(IResource.KEEP_HISTORY, progress); |
| removeState(); |
| } catch (CoreException exception) { |
| throw TeamPlugin.wrapException(exception); |
| } |
| } |
| |
| /** |
| * Make the local directories matching the description of the local resource state. |
| */ |
| protected final void mkLocalDirs(IProgressMonitor progress) throws TeamException { |
| try { |
| IResource resource = getLocal(); |
| if (resource.getType() == IResource.FILE) { |
| resource = resource.getParent(); |
| } |
| if (resource.getType() == IResource.FOLDER && ! resource.exists()) { |
| if (!resource.getParent().exists()) { |
| ResourceState parent=getResourceStateFor(resource.getParent()); |
| parent.mkLocalDirs(progress); |
| } |
| ((IFolder)resource).create(false /* force */, true /* make local */, progress); |
| // Mark the folders as having a base |
| storeState(); |
| } |
| } catch (CoreException exception) { |
| // The creation failed. |
| throw TeamPlugin.wrapException(exception); |
| } |
| } |
| |
| /** |
| * Get an array of local children of the given container, or an empty array if the |
| * container does not exist or has no children. |
| */ |
| protected final IResource[] getLocalChildren() throws TeamException { |
| // We are assuming that the resource is a container. |
| Assert.isLegal(getLocal() instanceof IContainer); |
| IContainer container = (IContainer)getLocal(); |
| if (container.exists()) |
| try { |
| return container.members(); |
| } catch (CoreException exception) { |
| throw TeamPlugin.wrapException(exception); |
| } |
| return new IResource[0]; |
| } |
| |
| /** |
| * Put the resource from the workspace to the remote provider. |
| * Assume that the 'local' resource is correct, and change the remote |
| * resource to look like the local resource. This includes removing any |
| * child resources that exist remotely but do not exist locally. |
| */ |
| protected final void put(IProgressMonitor progress) throws TeamException { |
| |
| progress = Policy.monitorFor(progress); |
| |
| // the no progress monitor is used to control the progress given to |
| // other methods and ensures that the progress monitor is not overloaded |
| // with subtask messages and work. The null monitor does propagate |
| // cancellation. |
| IProgressMonitor noProgress = new NullSubProgressMonitor(progress); |
| try { |
| // Check cancellation |
| progress.beginTask(null, 100); |
| Policy.checkCanceled(progress); |
| |
| // Ensure that the remote type matches the local type |
| boolean hasRemote = hasRemote(noProgress); |
| if ((getRemoteType() != localResource.getType() && localResource.getType() != IResource.PROJECT)) { |
| if (hasRemote) delete(noProgress); |
| hasRemote = false; |
| } |
| |
| // Upload the resource (this is a shallow operation for folders) |
| checkin(Policy.subMonitorFor(progress, 75)); |
| |
| // If we're putting a file, we're done |
| if (localResource.getType() == IResource.FILE) return; |
| |
| // If the local doesn't exist then we just deleted the remote so we're done |
| if (!hasLocal()) return; |
| |
| // Make a list of _remote_ children that have not yet been processed, |
| Map surplusRemoteChildren = new HashMap(); |
| if (hasRemote) { |
| ResourceState[] remoteChildren = remoteChildren = getRemoteChildren(progress); |
| for (int i = 0; i < remoteChildren.length; i++) { |
| ResourceState resourceState = remoteChildren[i]; |
| surplusRemoteChildren.put(resourceState.getLocal(), resourceState); |
| } |
| } |
| |
| // For each local child that is a file, make the remote file content equivalent. |
| IResource[] localChildren = getLocalChildren(); |
| IProgressMonitor subMonitor = Policy.subMonitorFor(progress, 25); |
| try { |
| subMonitor.beginTask(null, localChildren.length * 100); |
| for (int i = 0; i < localChildren.length; i++) { |
| IResource localChild = localChildren[i]; |
| // Get the resource state corresponding to the local resource |
| ResourceState state = (ResourceState)surplusRemoteChildren.get(localChild); |
| if (state == null) { |
| // There is no remote corresponding to the local |
| state = getResourceStateFor(localChild); |
| } else { |
| // There is a remote. Remember that we have processed this child. |
| surplusRemoteChildren.remove(localChild); |
| } |
| // Put the child (this is a deep operation for folders) |
| state.put(Policy.subMonitorFor(subMonitor, 100)); |
| } |
| } finally { |
| subMonitor.done(); |
| } |
| |
| // Remove each remote child that does not have a corresponding local resource. |
| Iterator childrenItr = surplusRemoteChildren.values().iterator(); |
| while (childrenItr.hasNext()) { |
| ResourceState unseenChild = (ResourceState) childrenItr.next(); |
| unseenChild.delete(noProgress); |
| } |
| } finally { |
| progress.done(); |
| |
| } |
| } |
| } |