Branch for FTP/DAV 2.0.1 candidates
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/target/IRemoteTargetResource.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/target/IRemoteTargetResource.java
index 9106a87..9f856f6 100644
--- a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/target/IRemoteTargetResource.java
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/target/IRemoteTargetResource.java
@@ -112,5 +112,10 @@
 	 * @return boolean
 	 */
 	boolean canBeReached(IProgressMonitor monitor) throws TeamException;
+	
+	/**
+	 * Delete the remote resource.
+	 */
+	public void delete(IProgressMonitor monitor) throws TeamException;
 
 }
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/target/ResourceState.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/target/ResourceState.java
index c11cb3f..0888240 100644
--- a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/target/ResourceState.java
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/target/ResourceState.java
@@ -689,11 +689,14 @@
 				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);
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/target/Site.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/target/Site.java
index b86bada..c0fc2f2 100644
--- a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/target/Site.java
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/target/Site.java
@@ -10,14 +10,23 @@
  ******************************************************************************/
 package org.eclipse.team.internal.core.target;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
+import java.net.MalformedURLException;
 import java.net.URL;
-import java.security.Provider;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
 
+import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
 import org.eclipse.team.core.TeamException;
-import org.eclipse.team.core.sync.IRemoteResource;
+import org.eclipse.team.internal.core.Assert;
 
 /**
  * A <code>Site</code> is a place where resources can be deployed and 
@@ -26,6 +35,9 @@
  * @see ISiteFactory
  */
 public abstract class Site {
+	
+	//The location of the site's resources:
+	private URL rootUrl;
 
 	/**
 	 * Answers a <code>TargetProvider</code> instance for the given path at 
@@ -45,24 +57,16 @@
 	public abstract String getType();
 	
 	/**
-	 * Answers the location of this site as a URL. For example:
-	 * <blockquote><pre>
-	 * http://www.mysite.com:14356/dav
-	 * </pre></blockquote>
-	 * 
-	 * @return URL location of this site
-	 */
-	public abstract URL getURL();
-	
-	/**
 	 * Answers a string that can be displayed to the user that represents
 	 * this site. For example:
 	 * <blockquote><pre>
 	 * http://usename@www.mysite.com/dav (WebDav)
  	 * </pre></blockquote>
 	 */
-	public abstract String getDisplayName();
-	
+	public String getDisplayName() {
+		return getURL().toExternalForm();
+	}
+
 	/**
 	 * Writes the state of this site such that the corresponding concrete
 	 * <code>ISiteFactory</code> class can restore the site.
@@ -95,15 +99,19 @@
 	 * @see Object#equals(Object)
 	 */
 	public boolean equals(Object other) {
-		if(this == other) return true;
-		if(! (other instanceof Site)) return false;
+		if (this == other) return true;
+		if (!(other instanceof Site)) return false;
 		Site location = (Site)other;
-		return getType().equals(location.getType()) && 
-				getURL().equals(location.getURL());
+		if (!getType().equals(location.getType())) return false;
+		URL url = getURL();
+		if (url == null) return super.equals(other);
+		return url.equals(location.getURL());
 	}
 	
 	public int hashCode() {
-		return getURL().hashCode();
+		URL url = getURL();
+		if (url == null) return super.hashCode();
+		return url.hashCode();
 	}
 
 	/**
@@ -114,4 +122,32 @@
 	public String toString() {
 		return getDisplayName();
 	}
+	
+	/**
+	 * Should be called whenever a site is being deleted from the workspace.
+	 * This method removes the encrypted login info stored for the site.
+	 * @throws TeamException
+	 */
+	public abstract void dispose() throws TeamException;
+
+	/**
+	 * Answers the location of this site as a URL. For example:
+	 * <blockquote><pre>
+	 * http://www.mysite.com:14356/dav
+	 * </pre></blockquote>
+	 * 
+	 * @return URL location of this site
+	 */
+	public URL getURL() {
+		return rootUrl;
+	}
+
+	/**
+	 * Sets the rootUrl.
+	 * @param rootUrl The rootUrl to set
+	 */
+	protected void setURL(URL rootUrl) {
+		this.rootUrl = rootUrl;
+	}
+
 }
\ No newline at end of file
diff --git a/bundles/org.eclipse.team.ui/plugin.properties b/bundles/org.eclipse.team.ui/plugin.properties
index bd63fcb..d47c143 100644
--- a/bundles/org.eclipse.team.ui/plugin.properties
+++ b/bundles/org.eclipse.team.ui/plugin.properties
@@ -9,6 +9,7 @@
 TextPreferencePage.name=File Content
 
 IgnorePreferencePage.name=Ignored Resources
+loginDetails=Login Details
 
 ConfigureProject.label=&Share Project...
 ConfigureProject.tooltip=Share the project with others using a version and configuration management system.
diff --git a/bundles/org.eclipse.team.ui/plugin.xml b/bundles/org.eclipse.team.ui/plugin.xml
index fbe567c..ee9f898 100644
--- a/bundles/org.eclipse.team.ui/plugin.xml
+++ b/bundles/org.eclipse.team.ui/plugin.xml
@@ -42,6 +42,17 @@
        	    category="org.eclipse.team.ui.TeamPreferences">
        </page>
    </extension>
+   
+<!-- ************** Property Pages *************** -->
+   <extension
+         point="org.eclipse.ui.propertyPages">
+      <page
+            name="%loginDetails"
+            objectClass="org.eclipse.team.internal.ui.target.SiteElement"
+            id="org.eclipse.team.target.ui.propertyPages.AuthenticatedTargetSitePropertiesPage"
+            class="org.eclipse.team.internal.ui.target.AuthenticatedTargetSitePropertiesPage">
+      </page>
+   </extension>
 
 <!-- ****************** POPUP ACTIONS *************** -->
 
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/messages.properties b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/messages.properties
index 6ab9af6..bb2a554 100644
--- a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/messages.properties
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/messages.properties
@@ -2,6 +2,11 @@
 # Message catalog for org.eclipse.team.ui
 ###############################################
 
+AuthenticatedTargetSitePropertiesPage.URL=URL:
+AuthenticatedTargetSitePropertiesPage.User=&User:
+AuthenticatedTargetSitePropertiesPage.Password=&Password:
+AuthenticatedTargetSitePropertiesPage.Error=Error Occurred
+
 CatchupReleaseViewer.expand=&Expand All
 CatchupReleaseViewer.ignoreWhiteSpace=&Ignore White Space
 CatchupReleaseViewer.refreshAction=&Refresh With Remote
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/ConfigureTargetWizard.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/ConfigureTargetWizard.java
index 2b5307d..fc969d6 100644
--- a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/ConfigureTargetWizard.java
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/ConfigureTargetWizard.java
@@ -250,7 +250,7 @@
 			try {				
 				TargetProvider provider = TargetManager.getProvider(project);
 				if(provider != null) {
-					if(! MessageDialog.openQuestion(container.getShell(),
+					if(!provider.getSite().equals(site) && !MessageDialog.openQuestion(container.getShell(),
 						Policy.bind("ConfigureTargetWizardQuestion_2"), //$NON-NLS-1$
 						Policy.bind("ConfigureTargetWizard.alreadyMapped", project.getName(), provider.getURL().toExternalForm()))) { //$NON-NLS-1$
 					return false;
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/DiscardSiteAction.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/DiscardSiteAction.java
index 8263a92..67a1f08 100644
--- a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/DiscardSiteAction.java
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/DiscardSiteAction.java
@@ -163,6 +163,7 @@
 				// and from the target manager.
 				for (int i = 0; i < sites.length; i++) {
 					TargetManager.removeSite(sites[i]);
+					sites[i].dispose();
 				}					
 			}
 		} catch (TeamException e) {
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/GetSyncAction.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/GetSyncAction.java
index 1c1e877..2f3b07e 100644
--- a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/GetSyncAction.java
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/GetSyncAction.java
@@ -16,7 +16,10 @@
 import java.util.List;
 import java.util.Set;
 
+import org.eclipse.compare.structuremergeviewer.Differencer;
+import org.eclipse.core.resources.IContainer;
 import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.jface.dialogs.ErrorDialog;
 import org.eclipse.jface.viewers.ISelectionProvider;
@@ -24,6 +27,7 @@
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.team.core.TeamException;
 import org.eclipse.team.internal.core.InfiniteSubProgressMonitor;
+import org.eclipse.team.internal.core.target.TargetManager;
 import org.eclipse.team.internal.core.target.TargetProvider;
 import org.eclipse.team.internal.ui.IHelpContextIds;
 import org.eclipse.team.internal.ui.Policy;
@@ -63,11 +67,43 @@
 			if (changed.length == 0) {
 				return syncSet;
 			}
-			List resources = new ArrayList();
+			List fileResources = new ArrayList();
+ 			List folderDeletions = new ArrayList();
+ 			List folderAdditions = new ArrayList();
+ 			//Find the incoming file changes the potential incoming folder deletions:
 			for (int i = 0; i < changed.length; i++) {
-				resources.add(changed[i].getResource());
+				if (changed[i].getChangeDirection()==ITeamNode.INCOMING || changed[i].getChangeDirection()==ITeamNode.CONFLICTING) {
+					if (changed[i].getResource().getType()==IResource.FILE) fileResources.add(changed[i].getResource());
+	 				else if (changed[i].getChangeType()==Differencer.DELETION 
+	 					&& /*don't delete nonexistant folders*/changed[i].getResource().exists()) 
+	 					folderDeletions.add(changed[i].getResource());
+	 				else {
+	 					//If the new remote folders have no children then we'd better explicitly create them locally:
+	 					IResource resource=changed[i].getResource();
+	 					if (getRemoteResourceFor(resource).members(monitor).length==0) 
+	 						folderAdditions.add(changed[i].getResource());
+	 				}
+				}
 			}
-			get((IResource[])resources.toArray(new IResource[resources.size()]), monitor);
+			get((IResource[])fileResources.toArray(new IResource[fileResources.size()]), monitor);
+			get((IResource[])folderAdditions.toArray(new IResource[folderDeletions.size()]), monitor);
+ 			if (folderDeletions.size()>0) {
+ 				//Prune the list of potential incoming folder deletions, retaining only those that don't have local content:
+	 			boolean delete;
+	 			Iterator iter=folderDeletions.iterator();
+	 			for (IContainer container=(IContainer)iter.next(); iter.hasNext(); container=(IContainer)iter.next()) {
+	 				delete=true;
+	 				IResource[] children=container.members();
+	 				for (int j = 0; j < children.length; j++) {
+	 					if (!folderDeletions.contains(children[j])) {
+	 						delete=false;
+	 						break;
+	 					}
+					}
+	 				if (!delete) folderDeletions.remove(container);
+	 			}
+	 			get((IResource[])folderDeletions.toArray(new IResource[folderDeletions.size()]), monitor);
+ 			}
 		} catch (final TeamException e) {
 			getShell().getDisplay().syncExec(new Runnable() {
 				public void run() {
@@ -75,6 +111,13 @@
 				}
 			});
 			return null;
+		} catch (final CoreException e) {
+ 			getShell().getDisplay().syncExec(new Runnable() {
+ 				public void run() {
+ 					ErrorDialog.openError(getShell(), null, null, e.getStatus());
+ 				}
+ 			});
+ 			return null;
 		}
 		return syncSet;
 	}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/PutSyncAction.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/PutSyncAction.java
index 52f6dc3..a26bff3 100644
--- a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/PutSyncAction.java
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/PutSyncAction.java
@@ -16,14 +16,21 @@
 import java.util.List;
 import java.util.Set;
 
+import org.eclipse.compare.structuremergeviewer.Differencer;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.jface.dialogs.ErrorDialog;
 import org.eclipse.jface.viewers.ISelectionProvider;
 import org.eclipse.jface.viewers.StructuredSelection;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.sync.IRemoteResource;
+import org.eclipse.team.core.sync.IRemoteSyncElement;
 import org.eclipse.team.internal.core.InfiniteSubProgressMonitor;
+import org.eclipse.team.internal.core.target.TargetManager;
 import org.eclipse.team.internal.core.target.TargetProvider;
 import org.eclipse.team.internal.ui.IHelpContextIds;
 import org.eclipse.team.internal.ui.Policy;
@@ -65,11 +72,40 @@
 			if (changed.length == 0) {
 				return syncSet;
 			}
-			List resources = new ArrayList();
+			List fileResources = new ArrayList();
+ 			List folderDeletions = new ArrayList();
+ 			List folderAdditions = new ArrayList();
+ 			//Find the outgoing file changes the potential outgoing folder deletions:
 			for (int i = 0; i < changed.length; i++) {
-				resources.add(changed[i].getResource());
+				if (changed[i].getChangeDirection()==ITeamNode.OUTGOING || changed[i].getChangeDirection()==ITeamNode.CONFLICTING) {
+					if (changed[i].getResource().getType()==IResource.FILE) fileResources.add(changed[i].getResource());
+	 				else if (changed[i].getChangeType()==Differencer.DELETION)
+	 					folderDeletions.add(changed[i].getResource());
+	 				else if (((IContainer)(changed[i].getResource())).members().length==0) 
+	 					////If the new local folders have no children then we'd better explicitly create them remotely:
+	 					folderAdditions.add(changed[i].getResource());
+				}
 			}
-			put((IResource[])resources.toArray(new IResource[resources.size()]), monitor);
+			put((IResource[])fileResources.toArray(new IResource[fileResources.size()]), monitor);
+			if (folderAdditions.size()>0) 
+				put((IResource[])folderAdditions.toArray(new IResource[folderDeletions.size()]), monitor);
+			if (folderDeletions.size()>0) {
+				//Prune the list of potential outgoing folder deletions, retaining only those that don't have remote content:
+	 			boolean delete;
+	 			Iterator iter=folderDeletions.iterator();
+	 			for (IContainer i=(IContainer)iter.next(); iter.hasNext(); i=(IContainer)iter.next()) {
+	 				delete=true;
+	 				IRemoteResource[] children=getRemoteResourceFor(i).members(monitor);
+	 				for (int j = 0; j < children.length; j++) {
+	 					if (!folderDeletions.contains(children[j])) {
+	 						delete=false;
+	 						break;
+	 					}
+	 				}
+	 				if (!delete) folderDeletions.remove(i);
+	 			}
+	 			put((IResource[])folderDeletions.toArray(new IResource[folderDeletions.size()]), monitor);
+			}
 		} catch (final TeamException e) {
 			getShell().getDisplay().syncExec(new Runnable() {
 				public void run() {
@@ -77,6 +113,13 @@
 				}
 			});
 			return null;
+		} catch (final CoreException e) {
+ 			getShell().getDisplay().syncExec(new Runnable() {
+ 				public void run() {
+ 					ErrorDialog.openError(getShell(), null, null, e.getStatus());
+ 				}
+ 			});
+ 			return null;
 		}
 		return syncSet;
 	}
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/SiteExplorerView.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/SiteExplorerView.java
index d2560f5..a863f39 100644
--- a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/SiteExplorerView.java
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/SiteExplorerView.java
@@ -47,10 +47,10 @@
 import org.eclipse.team.internal.ui.Policy;
 import org.eclipse.team.internal.ui.TeamUIPlugin;
 import org.eclipse.team.internal.ui.UIConstants;
-import org.eclipse.team.ui.ISharedImages;
 import org.eclipse.team.ui.TeamImages;
 import org.eclipse.ui.IActionBars;
 import org.eclipse.ui.IWorkbenchActionConstants;
+import org.eclipse.ui.dialogs.PropertyDialogAction;
 import org.eclipse.ui.help.WorkbenchHelp;
 import org.eclipse.ui.internal.WorkbenchImages;
 import org.eclipse.ui.model.WorkbenchLabelProvider;
@@ -88,7 +88,7 @@
 	// The view's actions
 	private Action addSiteAction;
 	private Action newFolderAction;
-	private Action deleteAction;
+	private PropertyDialogAction propertiesAction;
 	
 	/**
 	 * Sorter for the folderContents table
@@ -432,6 +432,22 @@
 		tbm.add(addSiteAction);
 		tbm.update(false);
 		
+		// Properties
+		propertiesAction = new PropertyDialogAction(shell, folderTree);
+		getViewSite().getActionBars().setGlobalActionHandler(IWorkbenchActionConstants.PROPERTIES, propertiesAction);		
+		IStructuredSelection selection = (IStructuredSelection)folderTree.getSelection();
+		if (selection.size() == 1 && selection.getFirstElement() instanceof SiteElement) {
+			propertiesAction.setEnabled(true);
+		} else {
+			propertiesAction.setEnabled(false);
+		}
+		folderTree.addSelectionChangedListener(new ISelectionChangedListener() {
+			public void selectionChanged(SelectionChangedEvent event) {
+				IStructuredSelection ss = (IStructuredSelection)event.getSelection();
+				boolean enabled = ss.size() == 1 && ss.getFirstElement() instanceof SiteElement;
+				propertiesAction.setEnabled(enabled);
+			}
+		});
 		
 		MenuManager treeMgr = new MenuManager();
 		MenuManager tableMgr = new MenuManager();
@@ -448,6 +464,7 @@
 				manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
 				sub.add(addSiteAction);
 				sub.add(newFolderAction);
+				manager.add(propertiesAction);
 			}
 		};
 		treeMgr.addMenuListener(menuListener);
diff --git a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/TargetSyncAction.java b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/TargetSyncAction.java
index b5baae6..18c5a59 100644
--- a/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/TargetSyncAction.java
+++ b/bundles/org.eclipse.team.ui/src/org/eclipse/team/internal/ui/target/TargetSyncAction.java
@@ -33,6 +33,7 @@
 import org.eclipse.jface.viewers.IStructuredSelection;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.sync.IRemoteResource;
 import org.eclipse.team.internal.core.target.TargetManager;
 import org.eclipse.team.internal.core.target.TargetProvider;
 import org.eclipse.team.internal.ui.Policy;
@@ -50,6 +51,10 @@
 	protected int syncMode;
 	private Shell shell;
 	
+	protected static IRemoteResource getRemoteResourceFor(IResource local) throws TeamException {
+		return TargetManager.getProvider(local.getProject()).getRemoteSyncElement(local).getRemote();
+	}
+	
 	/**
 	 * Creates a TargetSyncAction which works on selection and doesn't commit changes.
 	 */