r7508 - in dumbhippo/trunk/server: src/com/dumbhippo/persistence src/com/dumbhippo/server src/com/dumbhippo/server/impl src/com/dumbhippo/server/views src/com/dumbhippo/web/pages src/com/dumbhippo/web/servlets web/javascript/dh web/jsp-gnome web/jsp3 web/tags/3 web/tags/gnome



Author: marinaz
Date: 2008-09-19 19:31:07 -0500 (Fri, 19 Sep 2008)
New Revision: 7508

Modified:
   dumbhippo/trunk/server/src/com/dumbhippo/persistence/Account.java
   dumbhippo/trunk/server/src/com/dumbhippo/persistence/ExternalAccount.java
   dumbhippo/trunk/server/src/com/dumbhippo/persistence/OnlineAccountType.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/ExternalAccountSystem.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/HttpMethods.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/impl/ExternalAccountSystemBean.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/impl/FacebookTrackerBean.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/impl/HttpMethodsBean.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/views/ExternalAccountView.java
   dumbhippo/trunk/server/src/com/dumbhippo/web/pages/AccountPage.java
   dumbhippo/trunk/server/src/com/dumbhippo/web/servlets/FacebookServlet.java
   dumbhippo/trunk/server/web/javascript/dh/account.js
   dumbhippo/trunk/server/web/javascript/dh/lovehate.js
   dumbhippo/trunk/server/web/jsp-gnome/account.jsp
   dumbhippo/trunk/server/web/jsp3/account.jsp
   dumbhippo/trunk/server/web/tags/3/accountEditTableExternals.tag
   dumbhippo/trunk/server/web/tags/3/accountJavascriptSetup.tag
   dumbhippo/trunk/server/web/tags/gnome/loveEntry.tag
Log:
Allow multiple accounts of the same type per user.

Create mugshotEnabled flag for ExternalAccount to indicate which accounts are going to be used by Mugshot (for now only one per account type).

Pass around external account id to know which account is being edited.

Change the way we display accounts on the web to be more generic in preparation to supporting entering multiple accounts.

Modified: dumbhippo/trunk/server/src/com/dumbhippo/persistence/Account.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/persistence/Account.java	2008-09-15 16:15:39 UTC (rev 7507)
+++ dumbhippo/trunk/server/src/com/dumbhippo/persistence/Account.java	2008-09-20 00:31:07 UTC (rev 7508)
@@ -634,10 +634,21 @@
 	}
 	
 	@Transient
+	public Set<ExternalAccount> getMugshotEnabledExternalAccounts() {
+		Set<ExternalAccount> filtered = new HashSet<ExternalAccount>();
+		for (ExternalAccount a : getExternalAccounts()) {
+			if (a.isMugshotEnabled()) {
+				filtered.add(a);
+			}
+		}
+		return filtered;	
+	}
+
+	@Transient
 	private Set<ExternalAccount> getExternalAccountsBySentiment(Sentiment sentiment) {
 		Set<ExternalAccount> filtered = new HashSet<ExternalAccount>();
 		for (ExternalAccount a : getExternalAccounts()) {
-			if (a.getSentiment() == Sentiment.LOVE) {
+			if (a.getSentiment() == Sentiment.LOVE && a.isMugshotEnabled()) {
 				filtered.add(a);
 			}
 		}
@@ -657,13 +668,34 @@
 	@Transient
 	public ExternalAccount getExternalAccount(ExternalAccountType type) {
 		for (ExternalAccount a : getExternalAccounts()) {
-			if (a.getAccountType() == type) {
+			if (a.getAccountType() == type && a.isMugshotEnabled()) {
 				return a;
 			}
 		}
 		return null;
 	}
 
+	@Transient
+	public ExternalAccount getExternalAccount(String id) {
+		for (ExternalAccount a : getExternalAccounts()) {
+			if (a.getId() == Long.parseLong(id)) {
+				return a;
+			}
+		}
+		return null;
+	}
+
+	@Transient
+	public Set<ExternalAccount> getExternalAccounts(OnlineAccountType type) {
+		Set<ExternalAccount> externalAccounts = new HashSet<ExternalAccount>(); 
+		for (ExternalAccount a : getExternalAccounts()) {
+			if (a.getOnlineAccountType().equals(type)) {
+				externalAccounts.add(a);
+			}
+		}
+		return externalAccounts;
+	}
+	
 	@Column(nullable=true)
 	public String getStackFilter() {
 		return stackFilter;

Modified: dumbhippo/trunk/server/src/com/dumbhippo/persistence/ExternalAccount.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/persistence/ExternalAccount.java	2008-09-15 16:15:39 UTC (rev 7507)
+++ dumbhippo/trunk/server/src/com/dumbhippo/persistence/ExternalAccount.java	2008-09-20 00:31:07 UTC (rev 7508)
@@ -9,7 +9,6 @@
 import javax.persistence.JoinTable;
 import javax.persistence.ManyToMany;
 import javax.persistence.ManyToOne;
-import javax.persistence.Table;
 import javax.persistence.Transient;
 import javax.persistence.UniqueConstraint;
 
@@ -17,10 +16,6 @@
 import org.hibernate.annotations.CacheConcurrencyStrategy;
 
 @Entity
- Table(name="ExternalAccount", 
-		   uniqueConstraints = 
-			      { UniqueConstraint(columnNames={"account_id", "accountType"})}
-		   )
 @Cache(usage=CacheConcurrencyStrategy.TRANSACTIONAL)
 public class ExternalAccount extends DBUnique {
 	private static final long serialVersionUID = 1L;
@@ -41,10 +36,13 @@
 	private String quip;
 	// if the account has associated feeds, they go here
 	private Set<Feed> feeds;
+	// right now we should allow only up to one account of a given type to be Mugshot enabled
+	private boolean mugshotEnabled;
 	
 	public ExternalAccount() {
 		sentiment = Sentiment.INDIFFERENT;
 		feeds = new HashSet<Feed>();
+		mugshotEnabled = false;
 	}
 	
 	public ExternalAccount(ExternalAccountType accountType) {
@@ -63,7 +61,7 @@
 	}
 	
 	// TODO: eventually, we should get rid of this column and use OnlineAccountType 
-	@Column(nullable=false)
+	@Column(nullable=true)
 	public ExternalAccountType getAccountType() {
 		return accountType;
 	}
@@ -95,17 +93,26 @@
 	}
 
 	public String validateHandle(String handle) throws ValidationException {
-		String validatedHandle = accountType.canonicalizeHandle(handle);
-		if (accountType.requiresHandleIfLoved() && validatedHandle == null) {
-			String usedForSite = "";
-			if (!this.getAccountType().isInfoTypeProvidedBySite()) {
-				usedForSite = " " + this.getSiteName() + " account";
+		if (accountType != null) {
+			String validatedHandle = accountType.canonicalizeHandle(handle);
+			if (accountType.requiresHandleIfLoved() && validatedHandle == null) {
+				String usedForSite = "";
+				if (!this.getAccountType().isInfoTypeProvidedBySite()) {
+					usedForSite = " " + this.getSiteName() + " account";
+				}
+				
+				throw new ValidationException("If you love " + this.getSiteName() + " let us know by entering your " 
+						                      + this.getAccountType().getSiteUserInfoType() + usedForSite + ".");
+			}		
+			return validatedHandle;
+		} else {
+			if (handle.trim().length() == 0) {
+				throw new ValidationException("If you use " + this.getSiteName() + " let us know by entering your " 
+	                      + this.getOnlineAccountType().getUserInfoType() + ".");				
 			}
-			
-			throw new ValidationException("If you love " + this.getSiteName() + " let us know by entering your " 
-					                      + this.getAccountType().getSiteUserInfoType() + usedForSite);
+			return handle;
 		}
-		return validatedHandle;
+		
 	}
 	
 	// used when setting a handle for loved accounts
@@ -136,12 +143,16 @@
 
 	// used when setting an extra for loved accounts
 	public void setExtraValidating(String extra) throws ValidationException {
-		String validatedExtra = accountType.canonicalizeExtra(extra);
-		if (accountType.requiresExtraIfLoved() && validatedExtra == null) {
-			// this message should not normally be displayed to the user
-			throw new ValidationException("Setting an empty value for " + this.getSiteName() + " user info extra is not valid");
+		if (accountType != null) {
+		    String validatedExtra = accountType.canonicalizeExtra(extra);
+		    if (accountType.requiresExtraIfLoved() && validatedExtra == null) {
+			    // this message should not normally be displayed to the user
+			    throw new ValidationException("Setting an empty value for " + this.getSiteName() + " user info extra is not valid");
+		    }		
+		    this.extra = validatedExtra;
+		} else {
+			this.extra = extra;
 		}
-		this.extra = validatedExtra;
 	}
 	
 	@Column(nullable=true)
@@ -198,7 +209,16 @@
 		else
 			throw new RuntimeException("The external account has multiple feeds, not sure which feed you want me to return.");
 	}
+	
+	@Column(nullable=false)
+	public boolean isMugshotEnabled() {
+		return mugshotEnabled;
+	}
 
+	public void setMugshotEnabled(boolean mugshotEnabled) {
+		this.mugshotEnabled = mugshotEnabled;
+	}
+
 	public void setFeed(Feed feed) {
 		Set<Feed> feedsSet = new HashSet<Feed>();
 		feedsSet.add(feed);	
@@ -207,12 +227,12 @@
 	
 	@Override
 	public String toString() {
-		return "{" + sentiment + " accountType=" + accountType + " handle=" + handle + " extra=" + extra + " quip=" + quip + "}";
+		return "{" + sentiment + " onlineAccountType=" + onlineAccountType + " handle=" + handle + " extra=" + extra + " quip=" + quip + "}";
 	}
 		
 	@Transient
 	public String getSiteName() {
-		return accountType.getSiteName();
+		return onlineAccountType.getSiteName();
 	}
 	
 	@Transient
@@ -225,16 +245,15 @@
 	
 	@Transient
 	public String getSiteLink() {
-	    if (!accountType.getSiteLink().equals("")) {
-	    	return accountType.getSiteLink();
-	    } else {
-	    	return getLink();
-	    }
+	    return onlineAccountType.getSite();
 	}
 	
 	@Transient
 	public String getIconName() {
-		return accountType.getIconName();
+		if (accountType != null)
+		    return accountType.getIconName();
+		else
+			return "";
 	}
 	
 	@Transient
@@ -247,13 +266,17 @@
 	
 	@Transient
 	public boolean hasLovedAndEnabledType(ExternalAccountType type) {
-		return accountType == type && getSentiment() == Sentiment.LOVE &&
-		getAccount().isActive() && hasAccountInfo() &&
+		// checking for public page makes sure that we don't stack blocks for online.gnome.org users
+		return accountType != null && accountType == type && getSentiment() == Sentiment.LOVE &&
+		isMugshotEnabled() && getAccount().isActive() && getAccount().isPublicPage() && hasAccountInfo() &&
 		(!accountType.isAffectedByMusicSharing() || getAccount().isMusicSharingEnabledWithDefault());
 	}
 	
 	@Transient
 	public String getAccountInfo() {
+		if (accountType == null)
+			return getHandle();
+		
 		switch (accountType.getInfoSource()) {
 		case HANDLE:
 			return getHandle();
@@ -277,14 +300,7 @@
         // which they can copy and paste into the browser to verify that it's their account.  
 	@Transient
 	public String getUsername() {
-		switch (accountType.getInfoSource()) {
-		case HANDLE:
-			return getHandle();
-		case EXTRA:
-			return getExtra();
-		default:
-			return null;
-		}
+		return getAccountInfo();
 	}
 	
 	@Transient
@@ -300,14 +316,17 @@
 	 */
 	@Transient
 	public boolean hasAccountInfo() {
-		return accountType.getHasAccountInfo(handle, extra);
+		return accountType != null && accountType.getHasAccountInfo(handle, extra);
 	}
 	
 	public static int compare(ExternalAccount first, ExternalAccount second) {
-		// Equality should be impossible, someone should not have two of the same account.
-		// But we'll put it here in case the java sort algorithm somehow needs it (tough to imagine)
-		if (first.getAccountType() == second.getAccountType())
-			return 0;
+		if (first.getOnlineAccountType().equals(second.getAccountType()))
+			if (first.isMugshotEnabled())
+				return -1;
+			else if (second.isMugshotEnabled())
+				return 1;
+			else 
+			    return 0;
 		
 		// We want "my website" first, "blog" second, then everything alphabetized by the human-readable name.
 		
@@ -320,6 +339,6 @@
 		if (second.getAccountType() == ExternalAccountType.BLOG)
 			return 1;				
 		
-		return String.CASE_INSENSITIVE_ORDER.compare(first.getSiteName(), second.getSiteName());
+		return String.CASE_INSENSITIVE_ORDER.compare(first.getOnlineAccountType().getFullName(), second.getOnlineAccountType().getFullName());
 	}
 }

Modified: dumbhippo/trunk/server/src/com/dumbhippo/persistence/OnlineAccountType.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/persistence/OnlineAccountType.java	2008-09-15 16:15:39 UTC (rev 7507)
+++ dumbhippo/trunk/server/src/com/dumbhippo/persistence/OnlineAccountType.java	2008-09-20 00:31:07 UTC (rev 7508)
@@ -62,6 +62,14 @@
 				throw new ValidationException("Account type name can only contain lower case characters and an underscore.");
 		}
     	
+    	// the following rules ensure uniqueness when we create DOM ids based on the names by capitalizing first character and each 
+    	// character that is preceded by an underscore 
+    	if (name.startsWith("_") || name.endsWith("_"))
+    		throw new ValidationException("Account type name can't start or end with and underscore.");
+    	
+    	if (name.indexOf("__") >= 0) 
+    		throw new ValidationException("Account type name can't contain multiple underscores in a row.");
+    	
 		this.name = name;
 	}
 
@@ -142,4 +150,9 @@
 	public void setSupported(boolean supported) {
 		this.supported = supported;
 	}
+	
+	@Override
+	public String toString() {
+		return "OnlineAccountType " + name;
+	}
 }

Modified: dumbhippo/trunk/server/src/com/dumbhippo/server/ExternalAccountSystem.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/ExternalAccountSystem.java	2008-09-15 16:15:39 UTC (rev 7507)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/ExternalAccountSystem.java	2008-09-20 00:31:07 UTC (rev 7508)
@@ -21,7 +21,7 @@
 @Local
 public interface ExternalAccountSystem extends AccountStatusListener {
 	/**
-	 * Gets (creating if necessary) an ExternalAccount of the given type for the current user's viewpoint.
+	 * Gets (creating if necessary) a Mugshot enabled ExternalAccount of the given type for the current user's viewpoint.
 	 * 
 	 * If created, the account starts out as Sentiment.INDIFFERENT; if already existing you have to 
 	 * look at its current status.
@@ -32,8 +32,14 @@
 	 */
 	ExternalAccount getOrCreateExternalAccount(UserViewpoint viewpoint, ExternalAccountType type);
 	
+	// creates an ExternalAccount for the owner of the viepoint that is not Mugshot enabled
+	public ExternalAccount createExternalAccount(UserViewpoint viewpoint, OnlineAccountType type);
+	
+	// looks up an ExternalAccount with the given id for the owner of the viewpoint
+	public ExternalAccount lookupExternalAccount(UserViewpoint viewpoint, String id) throws NotFoundException;
+	
 	/**
-	 * Gets an external account for the given user, only if it already exists.
+	 * Gets an external account for the given user, only if it already exists and is Mugshot-enabled.
 	 * Takes viewpoint into account (though currently the viewpoint doesn't matter).
 	 *  
 	 * Keep in mind that you then have to check whether the account is loved or hated.
@@ -45,6 +51,19 @@
 	 * @throws NotFoundException if the user in question has no account of this type
 	 */
 	ExternalAccount lookupExternalAccount(Viewpoint viewpoint, User user, ExternalAccountType type) throws NotFoundException;
+
+	/**
+	 * Gets existing external accounts for the given user  of a specified type.
+	 * Takes viewpoint into account (though currently the viewpoint doesn't matter).
+	 *  
+	 * Keep in mind that you then have to check whether the accounts are loved or hated.
+	 *  
+	 * @param viewpoint current user, anonymous, system viewpoint
+	 * @param user user to get an account for
+	 * @param type online account type of accounts
+	 * @return the accounts
+	 */
+	public Set<ExternalAccount> lookupExternalAccounts(Viewpoint viewpoint, User user, OnlineAccountType type);
 	
 	/**
 	 * Checks whether the given external account exists, is loved, and the user's account is enabled. i.e. 
@@ -68,7 +87,6 @@
 	 */
 	public Set<ExternalAccountView> getExternalAccountViews(Viewpoint viewpoint, User user);
 	
-	
 	public ExternalAccountView getExternalAccountView(Viewpoint viewpoint, ExternalAccount externalAccount);
 	
 	public ExternalAccountView getExternalAccountView(Viewpoint viewpoint, User user, ExternalAccountType externalAccountType) throws NotFoundException;

Modified: dumbhippo/trunk/server/src/com/dumbhippo/server/HttpMethods.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/HttpMethods.java	2008-09-15 16:15:39 UTC (rev 7507)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/HttpMethods.java	2008-09-20 00:31:07 UTC (rev 7508)
@@ -310,8 +310,8 @@
 	 * @throws XmlMethodException
 	 */
 	@HttpContentTypes(HttpResponseData.XMLMETHOD)
-	@HttpParams( { "type" })
-	public void doRemoveExternalAccount(XmlBuilder xml, UserViewpoint viewpoint, String type) throws XmlMethodException;	
+	@HttpParams( { "id" })
+	public void doRemoveExternalAccount(XmlBuilder xml, UserViewpoint viewpoint, String id) throws XmlMethodException;	
 	
 	
 	/**
@@ -340,64 +340,68 @@
 	 * @throws XmlMethodException
 	 */
 	@HttpContentTypes(HttpResponseData.XMLMETHOD)
-	@HttpParams( { "nsid", "email" })
-	public void doSetFlickrAccount(XmlBuilder xml, UserViewpoint viewpoint, String nsid, String email) throws XmlMethodException;	
+	@HttpParams( { "id", "nsid", "email" })
+	public void doSetFlickrAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String nsid, String email) throws XmlMethodException;	
 	
 	@HttpContentTypes(HttpResponseData.XMLMETHOD)
-	@HttpParams( { "name" })
-	public void doSetMySpaceAccount(XmlBuilder xml, UserViewpoint viewpoint, String name) throws XmlMethodException, RetryException;	
+	@HttpParams( { "id", "name" })
+	public void doSetMySpaceAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String name) throws XmlMethodException, RetryException;	
 	
 	@HttpContentTypes(HttpResponseData.XMLMETHOD)
-	@HttpParams( { "urlOrName" })
-	public void doSetLinkedInAccount(XmlBuilder xml, UserViewpoint viewpoint, String urlOrName) throws XmlMethodException;
+	@HttpParams( { "id", "urlOrName" })
+	public void doSetLinkedInAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String urlOrName) throws XmlMethodException;
 
 	@HttpContentTypes(HttpResponseData.XMLMETHOD)
-	@HttpParams( { "urlOrName" })
-	public void doSetYouTubeAccount(XmlBuilder xml, UserViewpoint viewpoint, String urlOrName) throws XmlMethodException;
+	@HttpParams( { "id", "urlOrName" })
+	public void doSetYouTubeAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String urlOrName) throws XmlMethodException;
 	
 	@HttpContentTypes(HttpResponseData.XMLMETHOD)
-	@HttpParams( { "urlOrName" })	
-	public void doSetLastFmAccount(XmlBuilder xml, UserViewpoint viewpoint, String urlOrName) throws XmlMethodException;	
+	@HttpParams( { "id", "urlOrName" })	
+	public void doSetLastFmAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String urlOrName) throws XmlMethodException;	
 
 	@HttpContentTypes(HttpResponseData.XMLMETHOD)
-	@HttpParams( { "urlOrName" })	
-	public void doSetDeliciousAccount(XmlBuilder xml, UserViewpoint viewpoint, String urlOrName) throws XmlMethodException, RetryException;	
+	@HttpParams( { "id", "urlOrName" })	
+	public void doSetDeliciousAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String urlOrName) throws XmlMethodException, RetryException;	
 
 	@HttpContentTypes(HttpResponseData.XMLMETHOD)
-	@HttpParams( { "urlOrName" })	
-	public void doSetTwitterAccount(XmlBuilder xml, UserViewpoint viewpoint, String urlOrName) throws XmlMethodException, RetryException;	
+	@HttpParams( { "id", "urlOrName" })	
+	public void doSetTwitterAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String urlOrName) throws XmlMethodException, RetryException;	
 
 	@HttpContentTypes(HttpResponseData.XMLMETHOD)
-	@HttpParams( { "urlOrName" })	
-	public void doSetDiggAccount(XmlBuilder xml, UserViewpoint viewpoint, String urlOrName) throws XmlMethodException, RetryException;
+	@HttpParams( { "id", "urlOrName" })	
+	public void doSetDiggAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String urlOrName) throws XmlMethodException, RetryException;
 
 	@HttpContentTypes(HttpResponseData.XMLMETHOD)
-	@HttpParams( { "urlOrName" })	
-	public void doSetRedditAccount(XmlBuilder xml, UserViewpoint viewpoint, String urlOrName) throws XmlMethodException, RetryException;
+	@HttpParams( { "id", "urlOrName" })	
+	public void doSetRedditAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String urlOrName) throws XmlMethodException, RetryException;
 
 	@HttpContentTypes(HttpResponseData.XMLMETHOD)
-	@HttpParams( { "url" })
-	public void doSetNetflixAccount(XmlBuilder xml, UserViewpoint viewpoint, String url) throws XmlMethodException, RetryException;
+	@HttpParams( { "id", "url" })
+	public void doSetNetflixAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String url) throws XmlMethodException, RetryException;
 
 	@HttpContentTypes(HttpResponseData.XMLMETHOD)
-	@HttpParams( { "url" })
-	public void doSetGoogleReaderAccount(XmlBuilder xml, UserViewpoint viewpoint, String url) throws XmlMethodException, RetryException;	
+	@HttpParams( { "id", "url" })
+	public void doSetGoogleReaderAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String url) throws XmlMethodException, RetryException;	
 	
 	@HttpContentTypes(HttpResponseData.XMLMETHOD)
-	@HttpParams( { "urlOrName" })
-	public void doSetPicasaAccount(XmlBuilder xml, UserViewpoint viewpoint, String urlOrName) throws XmlMethodException, RetryException;
+	@HttpParams( { "id", "urlOrName" })
+	public void doSetPicasaAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String urlOrName) throws XmlMethodException, RetryException;
 
 	@HttpContentTypes(HttpResponseData.XMLMETHOD)
-	@HttpParams( { "urlOrUserId" })
-	public void doSetAmazonAccount(XmlBuilder xml, UserViewpoint viewpoint, String urlOrUserId) throws XmlMethodException;
+	@HttpParams( { "id", "urlOrUserId" })
+	public void doSetAmazonAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String urlOrUserId) throws XmlMethodException;
+
+	@HttpContentTypes(HttpResponseData.XMLMETHOD)
+	@HttpParams( { "type", "id", "value" })
+	public void doSetOnlineAccountValue(XmlBuilder xml, UserViewpoint viewpoint, String type, String id, String value) throws XmlMethodException;
 	
 	@HttpContentTypes(HttpResponseData.XMLMETHOD)
 	@HttpParams( { "url" })
 	public void doSetWebsiteAccount(XmlBuilder xml, UserViewpoint viewpoint, String url) throws XmlMethodException;
 	
 	@HttpContentTypes(HttpResponseData.XMLMETHOD)
-	@HttpParams( { "url" })
-	public void doSetRhapsodyAccount(XmlBuilder xml, UserViewpoint viewpoint, String url) throws XmlMethodException, RetryException;
+	@HttpParams( { "id", "url" })
+	public void doSetRhapsodyAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String url) throws XmlMethodException, RetryException;
     
 	@HttpContentTypes(HttpResponseData.XMLMETHOD)
 	@HttpParams( { "url" })

Modified: dumbhippo/trunk/server/src/com/dumbhippo/server/impl/ExternalAccountSystemBean.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/impl/ExternalAccountSystemBean.java	2008-09-15 16:15:39 UTC (rev 7507)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/impl/ExternalAccountSystemBean.java	2008-09-20 00:31:07 UTC (rev 7508)
@@ -23,7 +23,6 @@
 import com.dumbhippo.XmlBuilder;
 import com.dumbhippo.dm.ReadWriteSession;
 import com.dumbhippo.persistence.Account;
-import com.dumbhippo.persistence.Application;
 import com.dumbhippo.persistence.ExternalAccount;
 import com.dumbhippo.persistence.ExternalAccountType;
 import com.dumbhippo.persistence.OnlineAccountType;
@@ -36,7 +35,6 @@
 import com.dumbhippo.server.Notifier;
 import com.dumbhippo.server.PicasaUpdater;
 import com.dumbhippo.server.YouTubeUpdater;
-import com.dumbhippo.server.applications.ApplicationView;
 import com.dumbhippo.server.dm.DataService;
 import com.dumbhippo.server.dm.ExternalAccountDMO;
 import com.dumbhippo.server.dm.ExternalAccountKey;
@@ -105,15 +103,66 @@
 		if (external == null) {
 			external = new ExternalAccount(type);
 			external.setOnlineAccountType(getOnlineAccountType(type));
+			external.setMugshotEnabled(true);
 			external.setAccount(a);
 			em.persist(external);
-			a.getExternalAccounts().add(external);
+			a.getExternalAccounts().add(external);			
 			
 			notifier.onExternalAccountCreated(a.getOwner(), external);
 		}
 		return external;
 	}
 	
+	public ExternalAccount createExternalAccount(UserViewpoint viewpoint, OnlineAccountType type) {
+		Account a = viewpoint.getViewer().getAccount();
+		if (!em.contains(a))
+			throw new RuntimeException("detached account in getOrCreateExternalAccount");
+
+		ExternalAccount external = new ExternalAccount();
+		external.setOnlineAccountType(type);
+		external.setAccount(a);
+		em.persist(external);
+		a.getExternalAccounts().add(external);			
+			
+		notifier.onExternalAccountCreated(a.getOwner(), external);
+		return external;
+	}
+	
+	public ExternalAccount lookupExternalAccount(UserViewpoint viewpoint, String id) throws NotFoundException {
+		Account a = viewpoint.getViewer().getAccount();
+		if (!em.contains(a))
+			throw new RuntimeException("detached account in lookupExternalAccount");
+		
+		ExternalAccount external = a.getExternalAccount(id);
+		if (external == null)
+			throw new NotFoundException("ExternalAccount with id " +  id + " was not found for account " + a);
+		
+		return external;
+	}
+	
+	public ExternalAccount lookupExternalAccount(Viewpoint viewpoint, User user, ExternalAccountType type)
+	    throws NotFoundException {
+	    if (!em.contains(user.getAccount()))
+		    throw new RuntimeException("detached account in lookupExternalAccount()");
+	
+	    // Right now, external accounts are public, unlike email/aim resources which are friends only...
+	    // so we don't need to use the viewpoint. But here in case we want to add it later.
+	    ExternalAccount external = user.getAccount().getExternalAccount(type);
+	    if (external == null)
+		    throw new NotFoundException("No external account of type " + type + " for user " + user);
+	    else
+	    	return external;
+    }
+ 
+    public Set<ExternalAccount> lookupExternalAccounts(Viewpoint viewpoint, User user, OnlineAccountType type) {
+	    if (!em.contains(user.getAccount()))
+	        throw new RuntimeException("detached account in lookupExternalAccount()");
+
+        // Right now, external accounts are public, unlike email/aim resources which are friends only...
+        // so we don't need to use the viewpoint. But here in case we want to add it later.
+        return user.getAccount().getExternalAccounts(type);
+    }
+
 	public OnlineAccountType getOnlineAccountType(ExternalAccountType accountType) {
 		Query q = em.createQuery("SELECT oat FROM OnlineAccountType oat WHERE " +
 				                 "oat.accountType = " + accountType.ordinal());
@@ -188,20 +237,6 @@
 		em.persist(type);
 		return type;
     }
-
-	public ExternalAccount lookupExternalAccount(Viewpoint viewpoint, User user, ExternalAccountType type)
-		throws NotFoundException {
-		if (!em.contains(user.getAccount()))
-			throw new RuntimeException("detached account in lookupExternalAccount()");
-		
-		// Right now, external accounts are public, unlike email/aim resources which are friends only...
-		// so we don't need to use the viewpoint. But here in case we want to add it later.
-		ExternalAccount external = user.getAccount().getExternalAccount(type);
-		if (external == null)
-			throw new NotFoundException("No external account of type " + type + " for user " + user);
-		else
-			return external;
-	}
 	
 	public Set<ExternalAccountView> getExternalAccountViews(Viewpoint viewpoint, User user) {
 		// Right now we ignore the viewpoint, so this method is pretty pointless.
@@ -211,7 +246,7 @@
 		if (!em.contains(user.getAccount()))
 			throw new RuntimeException("detached account in getExternalAccounts()");
 		
-		Set<ExternalAccount> accounts = user.getAccount().getExternalAccounts();
+		Set<ExternalAccount> accounts = user.getAccount().getMugshotEnabledExternalAccounts();
 		//logger.debug("{} external accounts for user {}", accounts.size(), user);
 		
 		Set<ExternalAccountView> accountViews = new HashSet<ExternalAccountView>();

Modified: dumbhippo/trunk/server/src/com/dumbhippo/server/impl/FacebookTrackerBean.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/impl/FacebookTrackerBean.java	2008-09-15 16:15:39 UTC (rev 7507)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/impl/FacebookTrackerBean.java	2008-09-20 00:31:07 UTC (rev 7508)
@@ -807,7 +807,7 @@
             	imageTitle = imageTitle + ": " + a.getExternalAccount().getLinkText();
           			
             accountListingSb.append("<a target='_blank' href='" + a.getLink() + "'>" +
-					                 "<img src='" + config.getBaseUrlMugshot().toExternalForm() + "/images3/" + a.getIconName() + "' title='" + imageTitle + "' style='width: 16; height: 16; border: none; margin-right: 3px;'/>" +
+					                 "<img src='" + config.getBaseUrlMugshot().toExternalForm() + "/images3/" + a.getExternalAccountType().getIconName() + "' title='" + imageTitle + "' style='width: 16; height: 16; border: none; margin-right: 3px;'/>" +
 					                 "</a>");
 		}		
 

Modified: dumbhippo/trunk/server/src/com/dumbhippo/server/impl/HttpMethodsBean.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/impl/HttpMethodsBean.java	2008-09-15 16:15:39 UTC (rev 7507)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/impl/HttpMethodsBean.java	2008-09-20 00:31:07 UTC (rev 7508)
@@ -1505,7 +1505,7 @@
 		feedSystem.removeGroupFeed(viewpoint.getViewer(), group, feed);		
 	}
 
-	public void doSetRhapsodyAccount(XmlBuilder xml, UserViewpoint viewpoint, String urlOrIdStr) throws XmlMethodException, RetryException {
+	public void doSetRhapsodyAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String urlOrIdStr) throws XmlMethodException, RetryException {
 		String urlOrId = urlOrIdStr.trim();
 
 		String rhapUserId = StringUtils.findParamValueInUrl(urlOrId, "rhapUserId");		
@@ -1518,7 +1518,7 @@
 			}
 		}
 
-		ExternalAccount external = externalAccountSystem.getOrCreateExternalAccount(viewpoint, ExternalAccountType.RHAPSODY);
+		ExternalAccount external = getOrCreateExternalAccount(viewpoint, ExternalAccountType.RHAPSODY, id);
 		String validatedHandle = null;
 		try {
 			validatedHandle = external.validateHandle(rhapUserId);
@@ -1546,7 +1546,7 @@
 		feed.getAccounts().add(external);
 	}
 	
-	public void doSetNetflixAccount(XmlBuilder xml, UserViewpoint viewpoint, String urlOrIdStr) throws XmlMethodException, RetryException {
+	public void doSetNetflixAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String urlOrIdStr) throws XmlMethodException, RetryException {
 		String urlOrId = urlOrIdStr.trim();
 
 		String netflixUserId = StringUtils.findParamValueInUrl(urlOrId, "id");		
@@ -1559,7 +1559,7 @@
 			}
 		}
 		
-		ExternalAccount external = externalAccountSystem.getOrCreateExternalAccount(viewpoint, ExternalAccountType.NETFLIX);
+		ExternalAccount external = getOrCreateExternalAccount(viewpoint, ExternalAccountType.NETFLIX, id);
 		String validatedHandle = null;
 		try {
 			validatedHandle = external.validateHandle(netflixUserId);
@@ -1586,14 +1586,6 @@
 		external.setFeed(feed);
 		feed.getAccounts().add(external);			
 	}
-	
-	private ExternalAccountType parseExternalAccountType(String type) throws XmlMethodException {
-		try {
-			return ExternalAccountType.valueOf(type);
-		} catch (IllegalArgumentException e) {
-			throw new XmlMethodException(XmlMethodErrorCode.PARSE_ERROR, "Unknown external account type: " + type);
-		}
-	}
 
 	private String parseEmail(String email) throws XmlMethodException {
 		try {
@@ -1610,28 +1602,86 @@
 		// the polling task for it; also for MySpace we could check if there is a polling task for checking on
 		// a feed for a private profile and remove that task
 		
-		ExternalAccountType typeEnum = parseExternalAccountType(type);
-		ExternalAccount external = externalAccountSystem.getOrCreateExternalAccount(viewpoint, typeEnum);
-		externalAccountSystem.setSentiment(external, Sentiment.HATE);
-		if (quip != null) {
-			quip = quip.trim();
-			if (quip.length() == 0)
-				quip = null;
-		}
-		external.setQuip(quip);
-		DataService.currentSessionRW().changed(ExternalAccountDMO.class, new ExternalAccountKey(external), "quip");
+		OnlineAccountType onlineAccountType = null;
+		try {
+	        onlineAccountType = externalAccountSystem.lookupOnlineAccountTypeForName(type);
+	    } catch (NotFoundException e) {
+		    throw new RuntimeException(e.getMessage());
+	    }    
+	    if (onlineAccountType.getAccountType() != null) {
+	    	// This will make sure that there is one mugshotEnabled exernal account of a given type that is hated. 
+	    	// It will through an exception if there are other accounts of this type that are loved, even if they are
+	    	// not Mugshot enabled, so we eed to make sure it's not possible to hate an account type in the UI when
+	    	// you have accounts of this type listed.
+	    	Set<ExternalAccount> allExternals = externalAccountSystem.lookupExternalAccounts(viewpoint, viewpoint.getViewer(), onlineAccountType);
+	    	for (ExternalAccount external : allExternals) {
+	    		if (external.getSentiment() == Sentiment.LOVE) {
+	    			throw new RuntimeException("Can't allow hating an external account of type " + onlineAccountType + " because other loved accounts of this type exist.");
+	    		}
+	    	}
+	        ExternalAccount external = externalAccountSystem.getOrCreateExternalAccount(viewpoint, onlineAccountType.getAccountType());
+	        externalAccountSystem.setSentiment(external, Sentiment.HATE);
+	        if (quip != null) {
+		        quip = quip.trim();
+		        if (quip.length() == 0)
+			        quip = null;
+	        }
+	        external.setQuip(quip);
+	        DataService.currentSessionRW().changed(ExternalAccountDMO.class, new ExternalAccountKey(external), "quip");
+	    } else {
+	    	throw new RuntimeException("OnlineAccountType " + onlineAccountType + " did no have a corresponding ExternalAccountType.");
+	    }
 	}
 
-	public void doRemoveExternalAccount(XmlBuilder xml, UserViewpoint viewpoint, String type) throws XmlMethodException {
+	public void doRemoveExternalAccount(XmlBuilder xml, UserViewpoint viewpoint, String id) throws XmlMethodException {
 		
 		// FIXME for any external account that has a feed associated with it, we could try to remove the feed and
 		// the polling task for it; also for MySpace we could check if there is a polling task for checking on
 		// a feed for a private profile and remove that task
 		
-		ExternalAccountType typeEnum = parseExternalAccountType(type);
-		ExternalAccount external = externalAccountSystem.getOrCreateExternalAccount(viewpoint, typeEnum);
-		externalAccountSystem.setSentiment(external, Sentiment.INDIFFERENT);
+		if (id=="") {
+			// nothing to do, the account the user was editing didn't exist before
+			return;
+		}
+			
+        try {
+		    ExternalAccount external = externalAccountSystem.lookupExternalAccount(viewpoint, id);
+		    externalAccountSystem.setSentiment(external, Sentiment.INDIFFERENT);	
+	    } catch (NotFoundException e) {
+		    throw new RuntimeException(e.getMessage());
+	    }
 	}
+	
+	private ExternalAccount getOrCreateExternalAccount(UserViewpoint viewpoint, ExternalAccountType externalAccountType, String id) {
+		OnlineAccountType onlineAccountType = externalAccountSystem.getOnlineAccountType(externalAccountType);
+		
+	    ExternalAccount external = null;
+	    if (id == "mugshot") {
+	    	// id = "mugshot" means we want to set the value for the mugshot enabled account
+	    	external = externalAccountSystem.getOrCreateExternalAccount(viewpoint, externalAccountType);
+	    } else if (id == "") {
+		    try {
+		        ExternalAccount mugshotEnabledExternal = externalAccountSystem.lookupExternalAccount(viewpoint, viewpoint.getViewer(), ExternalAccountType.YOUTUBE);
+		        // we shouldn't allow hating an account type and having some accounts of that type listed, so we just override the value on the
+		        // hated account if one exists
+		        if (mugshotEnabledExternal.getSentiment() == Sentiment.INDIFFERENT || mugshotEnabledExternal.getSentiment() == Sentiment.HATE) {
+		    	    external = mugshotEnabledExternal;
+		        } else {
+		            external = externalAccountSystem.createExternalAccount(viewpoint, onlineAccountType);
+		        }
+		    } catch (NotFoundException e) {
+			    external = externalAccountSystem.getOrCreateExternalAccount(viewpoint, externalAccountType);
+		    }			
+	    } else {
+            try {
+		        external = externalAccountSystem.lookupExternalAccount(viewpoint, id);
+	        } catch (NotFoundException e) {
+		        throw new RuntimeException(e.getMessage());
+	        }
+	    }
+	    
+	    return external;
+	}
 
 	public void doFindFlickrAccount(XmlBuilder xml, UserViewpoint viewpoint, String email) throws XmlMethodException {
 		FlickrWebServices ws = new FlickrWebServices(8000, config);
@@ -1644,9 +1694,9 @@
 		xml.closeElement();
 	}
 
-	public void doSetFlickrAccount(XmlBuilder xml, UserViewpoint viewpoint, String nsid, String email) throws XmlMethodException {
-		ExternalAccount external = externalAccountSystem.getOrCreateExternalAccount(viewpoint, ExternalAccountType.FLICKR);		
-
+	public void doSetFlickrAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String nsid, String email) throws XmlMethodException {	
+		ExternalAccount external = getOrCreateExternalAccount(viewpoint, ExternalAccountType.FLICKR, id);
+		
 		email = parseEmail(email);
 		
 		try {
@@ -1658,8 +1708,8 @@
 		externalAccountSystem.setSentiment(external, Sentiment.LOVE);
 	}
 	
-	public void doSetMySpaceAccount(XmlBuilder xml, UserViewpoint viewpoint, String name) throws XmlMethodException, RetryException {
-		ExternalAccount external = externalAccountSystem.getOrCreateExternalAccount(viewpoint, ExternalAccountType.MYSPACE);		
+	public void doSetMySpaceAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String name) throws XmlMethodException, RetryException {
+		ExternalAccount external = getOrCreateExternalAccount(viewpoint, ExternalAccountType.MYSPACE, id);		
 		String friendId;		
 		boolean isPrivate;
 		try {
@@ -1698,7 +1748,7 @@
 		}	
 	}
 
-	public void doSetYouTubeAccount(XmlBuilder xml, UserViewpoint viewpoint, String urlOrName) throws XmlMethodException {
+	public void doSetYouTubeAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String urlOrName) throws XmlMethodException {
 		// Try to pull youtube name out of either a youtube profile url ("http://www.youtube.com/user/$username"; || "http://www.youtube.com/profile?user=$username";) or 
 		// just try using the thing as a username directly
 		String name = urlOrName.trim();
@@ -1714,7 +1764,8 @@
 		if (startsWithHttp(name))
 			throw new XmlMethodException(XmlMethodErrorCode.PARSE_ERROR, "Enter your public profile URL or just your username");
 		
-		ExternalAccount external = externalAccountSystem.getOrCreateExternalAccount(viewpoint, ExternalAccountType.YOUTUBE);
+		ExternalAccount external = getOrCreateExternalAccount(viewpoint, ExternalAccountType.YOUTUBE, id);
+		
 		try {
 			external.setHandleValidating(name);
 		} catch (ValidationException e) {
@@ -1725,7 +1776,7 @@
 		xml.appendTextNode("username", external.getHandle());
 	}
 	
-	public void doSetLastFmAccount(XmlBuilder xml, UserViewpoint viewpoint, String urlOrName) throws XmlMethodException {
+	public void doSetLastFmAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String urlOrName) throws XmlMethodException {
 		String name = urlOrName.trim();
 		String found = StringUtils.findPathElementAfter(name, "/user/");
 		if (found != null)
@@ -1734,7 +1785,7 @@
 		if (startsWithHttp(name))
 			throw new XmlMethodException(XmlMethodErrorCode.PARSE_ERROR, "Enter your public profile URL or just your username");
 		
-		ExternalAccount external = externalAccountSystem.getOrCreateExternalAccount(viewpoint, ExternalAccountType.LASTFM);
+		ExternalAccount external = getOrCreateExternalAccount(viewpoint, ExternalAccountType.LASTFM, id);
 		try {
 			external.validateHandle(name);
 		} catch (ValidationException e) {
@@ -1757,7 +1808,7 @@
 		xml.appendTextNode("username", external.getHandle());
 	}	
 	
-	public void doSetDeliciousAccount(XmlBuilder xml, UserViewpoint viewpoint, String urlOrName) throws XmlMethodException, RetryException {
+	public void doSetDeliciousAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String urlOrName) throws XmlMethodException, RetryException {
 		String name = urlOrName.trim();
 		// del.icio.us urls are just "http://del.icio.us/myusername";
 		
@@ -1768,7 +1819,7 @@
 		if (startsWithHttp(name))
 			throw new XmlMethodException(XmlMethodErrorCode.PARSE_ERROR, "Enter your public profile URL or just your username");
 		
-		ExternalAccount external = externalAccountSystem.getOrCreateExternalAccount(viewpoint, ExternalAccountType.DELICIOUS);
+		ExternalAccount external = getOrCreateExternalAccount(viewpoint, ExternalAccountType.DELICIOUS, id);
 		String validatedHandle = null;
 		try {
 			validatedHandle = external.validateHandle(name);
@@ -1798,7 +1849,7 @@
 		xml.appendTextNode("username", external.getHandle());
 	}
 	
-	public void doSetTwitterAccount(XmlBuilder xml, UserViewpoint viewpoint, String urlOrName) throws XmlMethodException, RetryException {
+	public void doSetTwitterAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String urlOrName) throws XmlMethodException, RetryException {
 		String name = urlOrName.trim();
 		
 		// Twitter urls are just "http://twitter.com/myusername";
@@ -1810,7 +1861,7 @@
 		if (startsWithHttp(name))
 			throw new XmlMethodException(XmlMethodErrorCode.PARSE_ERROR, "Enter your public profile URL or just your username");
 		
-		ExternalAccount external = externalAccountSystem.getOrCreateExternalAccount(viewpoint, ExternalAccountType.TWITTER);
+		ExternalAccount external = getOrCreateExternalAccount(viewpoint, ExternalAccountType.TWITTER, id);
 		String validatedHandle = null;
 		try {
 			validatedHandle = external.validateHandle(name);
@@ -1848,7 +1899,7 @@
 	    }
 	}
 
-	public void doSetDiggAccount(XmlBuilder xml, UserViewpoint viewpoint, String urlOrName) throws XmlMethodException, RetryException {
+	public void doSetDiggAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String urlOrName) throws XmlMethodException, RetryException {
 		String name = urlOrName.trim();
 		
 		// Digg urls are "http://digg.com/users/myusername/stuff";
@@ -1860,7 +1911,7 @@
 		if (startsWithHttp(name))
 			throw new XmlMethodException(XmlMethodErrorCode.PARSE_ERROR, "Enter your public profile URL or just your username");
 		
-		ExternalAccount external = externalAccountSystem.getOrCreateExternalAccount(viewpoint, ExternalAccountType.DIGG);
+		ExternalAccount external = getOrCreateExternalAccount(viewpoint, ExternalAccountType.DIGG, id);
 		String validatedHandle = null;
 		try {
 			validatedHandle = external.validateHandle(name);
@@ -1891,7 +1942,7 @@
 		xml.appendTextNode("username", external.getHandle());
 	}
 
-	public void doSetRedditAccount(XmlBuilder xml, UserViewpoint viewpoint, String urlOrName) throws XmlMethodException, RetryException {
+	public void doSetRedditAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String urlOrName) throws XmlMethodException, RetryException {
 		String name = urlOrName.trim();
 
 		// Reddit urls are "http://reddit.com/user/myusername";
@@ -1907,7 +1958,7 @@
 		if (startsWithHttp(name))
 			throw new XmlMethodException(XmlMethodErrorCode.PARSE_ERROR, "Enter your public profile URL or just your username");
 		
-		ExternalAccount external = externalAccountSystem.getOrCreateExternalAccount(viewpoint, ExternalAccountType.REDDIT);
+		ExternalAccount external = getOrCreateExternalAccount(viewpoint, ExternalAccountType.REDDIT, id);
 		String validatedHandle = null;
 		try {
 			validatedHandle = external.validateHandle(name);
@@ -1965,7 +2016,7 @@
 	    }
 	}
 	
-	public void doSetLinkedInAccount(XmlBuilder xml, UserViewpoint viewpoint, String urlOrName) throws XmlMethodException {
+	public void doSetLinkedInAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String urlOrName) throws XmlMethodException {
 		// Try to pull linked in name out of either a linked in profile url ("http://www.linkedin.com/in/username";) or 
 		// just try using the thing as a username directly
 		String name = urlOrName.trim();
@@ -1978,7 +2029,7 @@
 		if (startsWithHttp(name))
 			throw new XmlMethodException(XmlMethodErrorCode.PARSE_ERROR, "Enter your public profile URL or just your username");
 		
-		ExternalAccount external = externalAccountSystem.getOrCreateExternalAccount(viewpoint, ExternalAccountType.LINKED_IN);
+		ExternalAccount external = getOrCreateExternalAccount(viewpoint, ExternalAccountType.LINKED_IN, id);
 		try {
 			external.setHandleValidating(name);
 		} catch (ValidationException e) {
@@ -1989,7 +2040,7 @@
 		xml.appendTextNode("username", external.getHandle());
 	}
 
-	public void doSetPicasaAccount(XmlBuilder xml, UserViewpoint viewpoint, String urlOrName) throws XmlMethodException, RetryException {
+	public void doSetPicasaAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String urlOrName) throws XmlMethodException, RetryException {
 		String name = urlOrName.trim();
 		
 		// Picasa public urls are http://picasaweb.google.com/username
@@ -2001,7 +2052,7 @@
 		if (startsWithHttp(name))
 			throw new XmlMethodException(XmlMethodErrorCode.PARSE_ERROR, "Enter your public gallery URL or just your username");
 		
-		ExternalAccount external = externalAccountSystem.getOrCreateExternalAccount(viewpoint, ExternalAccountType.PICASA);
+		ExternalAccount external = getOrCreateExternalAccount(viewpoint, ExternalAccountType.PICASA, id);
 		String validatedHandle = null;
 		try {
 			validatedHandle = external.validateHandle(name);
@@ -2035,7 +2086,7 @@
 		xml.appendTextNode("username", external.getHandle());
 	}
 	
-	public void doSetAmazonAccount(XmlBuilder xml, UserViewpoint viewpoint, String urlOrUserIdStr) throws XmlMethodException {		
+	public void doSetAmazonAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String urlOrUserIdStr) throws XmlMethodException {		
 		String urlOrUserId = urlOrUserIdStr.trim();
 
 		String amazonUserId = StringUtils.findPathElementAfter(urlOrUserId, "/profile/");
@@ -2048,7 +2099,7 @@
 			}
 		}
 		
-		ExternalAccount external = externalAccountSystem.getOrCreateExternalAccount(viewpoint, ExternalAccountType.AMAZON);
+		ExternalAccount external = getOrCreateExternalAccount(viewpoint, ExternalAccountType.AMAZON, id);
 		try {
 			external.validateHandle(amazonUserId);
 		} catch (ValidationException e) {
@@ -2094,14 +2145,43 @@
 		xml.closeElement();
 	}
 	
+	public void doSetOnlineAccountValue(XmlBuilder xml, UserViewpoint viewpoint, String type, String id, String value) throws XmlMethodException {	
+		OnlineAccountType onlineAccountType = null;
+		try {
+	        onlineAccountType = externalAccountSystem.lookupOnlineAccountTypeForName(type);
+	    } catch (NotFoundException e) {
+		    throw new RuntimeException(e.getMessage());
+	    }    
+	        
+		ExternalAccount external;
+		if (id == "") {
+			external = externalAccountSystem.createExternalAccount(viewpoint, onlineAccountType);
+		} else {
+	        try {
+			    external = externalAccountSystem.lookupExternalAccount(viewpoint, id);
+		    } catch (NotFoundException e) {
+			    throw new RuntimeException(e.getMessage());
+		    }
+		}
+		
+		try {
+			external.setHandleValidating(value);
+		} catch (ValidationException e) {
+			throw new XmlMethodException(XmlMethodErrorCode.PARSE_ERROR, e.getMessage());
+		}
+		externalAccountSystem.setSentiment(external, Sentiment.LOVE);
+		
+		xml.appendTextNode("username", external.getHandle());		
+	}
+	
 	public void doSetWebsiteAccount(XmlBuilder xml, UserViewpoint viewpoint, String urlStr) throws XmlMethodException {
 		// DO NOT cut and paste this block into similar external account methods. It's only here because
 		// we don't use the "love hate" widget on /account for the website, and the javascript glue 
 		// for the plain entries assumes this works.
 		if (urlStr == null || urlStr.trim().length() == 0) {
-			doRemoveExternalAccount(xml, viewpoint, "WEBSITE");
 			try {
 				ExternalAccount external = externalAccountSystem.lookupExternalAccount(viewpoint, viewpoint.getViewer(), ExternalAccountType.WEBSITE);
+				externalAccountSystem.setSentiment(external, Sentiment.INDIFFERENT);
 				// otherwise the website url would keep "coming back" since there's no visual indication of hate/indifferent status
 				external.setHandle(null);
 			} catch (NotFoundException e) {
@@ -2138,9 +2218,9 @@
 		// we don't use the "love hate" widget on /account for the website, and the javascript glue 
 		// for the plain entries assumes this works.
 		if (urlStr == null || urlStr.trim().length() == 0) {
-			doRemoveExternalAccount(xml, viewpoint, "BLOG");
 			try {
 				ExternalAccount external = externalAccountSystem.lookupExternalAccount(viewpoint, viewpoint.getViewer(), ExternalAccountType.BLOG);
+				externalAccountSystem.setSentiment(external, Sentiment.INDIFFERENT);	
 				// otherwise the blog url would keep "coming back" since there's no visual indication of hate/indifferent status
 				external.setHandle(null);
 			} catch (NotFoundException e) {
@@ -2176,7 +2256,7 @@
 		feed.getAccounts().add(external);
 	}
 	
-	public void doSetGoogleReaderAccount(XmlBuilder xml, UserViewpoint viewpoint, String feedOrPageUrl) throws XmlMethodException, RetryException {
+	public void doSetGoogleReaderAccount(XmlBuilder xml, UserViewpoint viewpoint, String id, String feedOrPageUrl) throws XmlMethodException, RetryException {
 		feedOrPageUrl = feedOrPageUrl.trim();
 		
 		if (feedOrPageUrl.startsWith("https://";))
@@ -2203,7 +2283,7 @@
 			logger.debug("Parsed google reader id '{}' from '{}'", userId, feedOrPageUrl);
 		}
 		
-		ExternalAccount external = externalAccountSystem.getOrCreateExternalAccount(viewpoint, ExternalAccountType.GOOGLE_READER);
+		ExternalAccount external = getOrCreateExternalAccount(viewpoint, ExternalAccountType.GOOGLE_READER, id);
 		String validatedHandle = null;
 		try {
 			validatedHandle = external.validateHandle(userId);

Modified: dumbhippo/trunk/server/src/com/dumbhippo/server/views/ExternalAccountView.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/views/ExternalAccountView.java	2008-09-15 16:15:39 UTC (rev 7507)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/views/ExternalAccountView.java	2008-09-20 00:31:07 UTC (rev 7508)
@@ -10,11 +10,13 @@
 import com.dumbhippo.persistence.ExternalAccount;
 import com.dumbhippo.persistence.ExternalAccountType;
 import com.dumbhippo.persistence.Feed;
+import com.dumbhippo.persistence.OnlineAccountType;
 import com.dumbhippo.persistence.Sentiment;
 
 public class ExternalAccountView {
     private ExternalAccount externalAccount;
     private ExternalAccountType externalAccountType;
+    private OnlineAccountType onlineAccountType;
     private String link;
     private Thumbnails thumbnails;
     
@@ -25,13 +27,20 @@
 		
 		@Override
 		public String getTotalThumbnailItemsString() {
-			return externalAccount.getAccountType().formatThumbnailCount(getTotalThumbnailItems());
+			if (externalAccount.getAccountType() != null) {
+			    return externalAccount.getAccountType().formatThumbnailCount(getTotalThumbnailItems());
+			} else if (getTotalThumbnailItems() == 1) {
+			    return getTotalThumbnailItems() + " item";
+			} else {
+				return getTotalThumbnailItems() + " items";
+			}
 		}
 	}
     
     public ExternalAccountView(ExternalAccount externalAccount) {
     	this.externalAccount = externalAccount;
     	this.externalAccountType = externalAccount.getAccountType();
+    	this.onlineAccountType = externalAccount.getOnlineAccountType();
     }
     
     public ExternalAccountView(ExternalAccount externalAccount, String link) {
@@ -44,8 +53,9 @@
      * ExternalAccountType, not ExternalAccountView.
      * @param externalAccountType
      */
-    public ExternalAccountView(ExternalAccountType externalAccountType) {
-    	this.externalAccountType = externalAccountType;
+    public ExternalAccountView(OnlineAccountType onlineAccountType) {
+    	this.onlineAccountType = onlineAccountType;
+    	this.externalAccountType = onlineAccountType.getAccountType();
     }
     
 	public ExternalAccount getExternalAccount() {
@@ -55,27 +65,59 @@
 	public ExternalAccountType getExternalAccountType() {
 		return externalAccountType;
 	}
+	
+	public OnlineAccountType getOnlineAccountType() {
+		return onlineAccountType;
+	}
 
 	public String getSiteName() {
-		return externalAccountType.getSiteName();
+		return onlineAccountType.getSiteName();
 	}
 	
 	public String getDomNodeIdName() {	
-		return externalAccountType.getDomNodeIdName();
+		String name = onlineAccountType.getName();
+		String idName = "";
+		int startIndex = 0;
+		
+		while (startIndex < name.length() && name.indexOf("_", startIndex) >= 0) {
+			int underscoreIndex = name.indexOf("_", startIndex);
+			if (startIndex < underscoreIndex)
+			    idName = idName + name.substring(startIndex, startIndex+1).toUpperCase() + name.substring(startIndex+1, underscoreIndex);
+			startIndex = underscoreIndex + 1;
+		}
+		if (startIndex+1 == name.length())
+		    idName = idName + name.substring(startIndex, startIndex+1).toUpperCase();
+        else if (startIndex+1 < name.length())
+        	idName = idName + name.substring(startIndex, startIndex+1).toUpperCase() + name.substring(startIndex+1);
+		    
+		if (externalAccount != null)
+		    return idName + externalAccount.getId();
+		else
+			return idName;
 	}
 	
-	public String getSiteUserInfoType() {
-		return externalAccountType.getSiteUserInfoType();
+	public String getId() {
+		if (externalAccount != null)
+		    return String.valueOf(externalAccount.getId());
+		else
+			return "";		
 	}
 
-	public boolean isInfoTypeProvidedBySite() {
-		return externalAccountType.isInfoTypeProvidedBySite();
+	public String getHateQuip() {
+		if (externalAccount != null)
+		    return externalAccount.getQuip();
+		else
+			return "";		
 	}
 	
-	public String getIconName() {
-		return externalAccountType.getIconName();
+	public boolean isMugshotEnabled() {
+	    return (externalAccount != null) && externalAccount.isMugshotEnabled();
 	}
 	
+	public String getUserInfoType() {
+		return onlineAccountType.getUserInfoType();
+	}
+	
 	public String getSentiment() {
 		if (externalAccount != null)
 		     return externalAccount.getSentiment().name().toLowerCase();
@@ -83,6 +125,20 @@
 		return Sentiment.INDIFFERENT.name().toLowerCase();
 	}
 	
+	public String getUsername() {
+		if (externalAccount != null && externalAccount.hasAccountInfo() && externalAccount.getUsername() != null)
+			return externalAccount.getUsername();
+		
+		return "";
+	}
+	
+	public String getIconName() {
+		if (externalAccountType != null)
+		    return externalAccountType.getIconName();
+		else
+			return "";
+	}
+	
 	public String getLink() {
 		if (link != null)
 		    return link;
@@ -130,7 +186,7 @@
 	
 	public void writeToXmlBuilder(XmlBuilder builder) {
 		builder.openElement("externalAccount",
-				"type", getExternalAccount().getAccountType().name(),
+				"type", getOnlineAccountType().getName(),
 				"sentiment", getSentiment(),
 				"icon", "/images3/" + getExternalAccount().getIconName(),
 				// The following will not be added unless the account is loved and enabled

Modified: dumbhippo/trunk/server/src/com/dumbhippo/web/pages/AccountPage.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/web/pages/AccountPage.java	2008-09-15 16:15:39 UTC (rev 7507)
+++ dumbhippo/trunk/server/src/com/dumbhippo/web/pages/AccountPage.java	2008-09-20 00:31:07 UTC (rev 7508)
@@ -2,11 +2,13 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 import org.slf4j.Logger;
 
 import com.dumbhippo.GlobalSetup;
 import com.dumbhippo.Pair;
+import com.dumbhippo.SortUtils;
 import com.dumbhippo.persistence.ExternalAccount;
 import com.dumbhippo.persistence.ExternalAccountType;
 import com.dumbhippo.persistence.FacebookAccount;
@@ -51,6 +53,8 @@
 	private String facebookAuthToken;
 	private String facebookErrorMessage;
 	private AmazonUpdater amazonUpdater;
+	private ListBean<ExternalAccountView> supportedAccountsListBean;
+	private ListBean<ExternalAccountView> gnomeSupportedAccountsListBean;
 	
 	public AccountPage() {
 		claimVerifier = WebEJBUtil.defaultLookup(ClaimVerifier.class);
@@ -61,6 +65,8 @@
 		facebookAuthToken = null;
 		facebookErrorMessage = null;
 		amazonUpdater = WebEJBUtil.defaultLookup(AmazonUpdater.class);
+		supportedAccountsListBean = null;
+		gnomeSupportedAccountsListBean = null;
 	}
 	
 	public SigninBean getSignin() {
@@ -99,22 +105,6 @@
 		return signin.getUser().getAccount().getHasPassword();
 	}
 	
-	public String getRhapsodyListeningHistoryFeedUrl() {
-		ExternalAccount external;
-		try {
-			external = externalAccounts.lookupExternalAccount(signin.getViewpoint(), signin.getUser(), ExternalAccountType.RHAPSODY);
-            if (external.getFeed() != null)
-			    return external.getFeed().getSource().getUrl();
-		} catch (NotFoundException e) {
-			// nothing to do
-		}
-		return ""; 
-	}
-	
-	public String getRhapsodyHateQuip() {
-		return getExternalAccountHateQuip(ExternalAccountType.RHAPSODY);
-	}
-	
 	private String getExternalAccountSentiment(ExternalAccountType type) {
 		ExternalAccount external;
 		try {
@@ -126,17 +116,6 @@
 		return external.getSentiment().name().toLowerCase();
 	}
 	
-	private String getExternalAccountHateQuip(ExternalAccountType type) {
-		ExternalAccount external;
-		try {
-			external = externalAccounts.lookupExternalAccount(signin.getViewpoint(), signin.getUser(), type);
-		} catch (NotFoundException e) {
-			return "";
-		}
-		
-		return external.getQuip();
-	}
-	
 	// don't export the "Handle" vague name to the .jsp please, change the bean getter 
 	// to be something legible like "email" or "userid" or whatever it is for the account type
 	private String getExternalAccountHandle(ExternalAccountType type) {
@@ -149,242 +128,101 @@
 		return external.getHandle();
 	}
 
-	// don't export the "Extra" vague name to the .jsp please, change the bean getter 
-	// to be something legible like "email" or "userid" or whatever it is for the account type
-	private String getExternalAccountExtra(ExternalAccountType type) {
-		ExternalAccount external;
-		try {
-			external = externalAccounts.lookupExternalAccount(signin.getViewpoint(), signin.getUser(), type);
-		} catch (NotFoundException e) {
-			return "";
-		}
-		return external.getExtra();
-	}
-
-	public String getMySpaceSentiment() {
-		return getExternalAccountSentiment(ExternalAccountType.MYSPACE);
-	}
-	
-	public String getMySpaceHateQuip() {
-		return getExternalAccountHateQuip(ExternalAccountType.MYSPACE);
-	}
-	
-	public String getMySpaceName() {
-		return getExternalAccountHandle(ExternalAccountType.MYSPACE);
-	}
-	
-	public String getFlickrSentiment() {
-		return getExternalAccountSentiment(ExternalAccountType.FLICKR);
-	}
-	
-	public String getFlickrHateQuip() {
-		return getExternalAccountHateQuip(ExternalAccountType.FLICKR);
-	}
-	
-	public String getFlickrEmail() {
-		return getExternalAccountExtra(ExternalAccountType.FLICKR);
-	}
-	
-	public String getLinkedInSentiment() {
-		return getExternalAccountSentiment(ExternalAccountType.LINKED_IN);
-	}
-	
-	public String getLinkedInHateQuip() {
-		return getExternalAccountHateQuip(ExternalAccountType.LINKED_IN);
-	}
-	
-	public String getLinkedInName() {
-		return getExternalAccountHandle(ExternalAccountType.LINKED_IN);
-	}
-
-	public String getYouTubeSentiment() {
-		return getExternalAccountSentiment(ExternalAccountType.YOUTUBE);
-	}
-	
-	public String getYouTubeHateQuip() {
-		return getExternalAccountHateQuip(ExternalAccountType.YOUTUBE);
-	}
-	
-	public String getYouTubeName() {
-		return getExternalAccountHandle(ExternalAccountType.YOUTUBE);
-	}
-
-	public String getLastFmSentiment() {
-		return getExternalAccountSentiment(ExternalAccountType.LASTFM);
-	}
-	
-	public String getLastFmHateQuip() {
-		return getExternalAccountHateQuip(ExternalAccountType.LASTFM);
-	}
-	
-	public String getLastFmName() {
-		return getExternalAccountHandle(ExternalAccountType.LASTFM);
-	}	
-	
-	public String getDeliciousSentiment() {
-		return getExternalAccountSentiment(ExternalAccountType.DELICIOUS);
-	}
-	
-	public String getDeliciousHateQuip() {
-		return getExternalAccountHateQuip(ExternalAccountType.DELICIOUS);
-	}
-	
-	public String getDeliciousName() {
-		return getExternalAccountHandle(ExternalAccountType.DELICIOUS);
-	}
-	
-	public String getTwitterSentiment() {
-		return getExternalAccountSentiment(ExternalAccountType.TWITTER);
-	}
-	
-	public String getTwitterHateQuip() {
-		return getExternalAccountHateQuip(ExternalAccountType.TWITTER);
-	}
-	
-	public String getTwitterName() {
-		return getExternalAccountHandle(ExternalAccountType.TWITTER);
-	}
-	
-	public String getDiggSentiment() {
-		return getExternalAccountSentiment(ExternalAccountType.DIGG);
-	}
-	
-	public String getDiggHateQuip() {
-		return getExternalAccountHateQuip(ExternalAccountType.DIGG);
-	}
-	
-	public String getDiggName() {
-		return getExternalAccountHandle(ExternalAccountType.DIGG);
-	}
-
-	public String getRedditSentiment() {
-		return getExternalAccountSentiment(ExternalAccountType.REDDIT);
-	}
-	
-	public String getRedditHateQuip() {
-		return getExternalAccountHateQuip(ExternalAccountType.REDDIT);
-	}
-	
-	public String getRedditName() {
-		return getExternalAccountHandle(ExternalAccountType.REDDIT);
-	}
-
-	public String getNetflixFeedUrl() {
-		ExternalAccount external;
-		try {
-			external = externalAccounts.lookupExternalAccount(signin.getViewpoint(), signin.getUser(), ExternalAccountType.NETFLIX);
-			// TODO: remove returning handle, that's temporary
-            if (external.getFeed() != null)
-			    return external.getFeed().getSource().getUrl();
-            else
-            	return external.getHandle();
-		} catch (NotFoundException e) {
-			// nothing to do
-		}
-		return ""; 
-	}
-	
-	public String getNetflixHateQuip() {
-		return getExternalAccountHateQuip(ExternalAccountType.NETFLIX);
-	}
-
-	public String getGoogleReaderUrl() {
-		ExternalAccount external;
-		try {
-			external = externalAccounts.lookupExternalAccount(signin.getViewpoint(), signin.getUser(), ExternalAccountType.GOOGLE_READER);
-			if (external.hasAccountInfo()) {
-			    return external.getLink();
-			}
-		} catch (NotFoundException e) {
-			// nothing to do
-		}
-		return ""; 
-	}
-	
-	public String getGoogleReaderHateQuip() {
-		return getExternalAccountHateQuip(ExternalAccountType.GOOGLE_READER);
-	}	
-
-	public String getGoogleReaderSentiment() {
-		return getExternalAccountSentiment(ExternalAccountType.GOOGLE_READER);
-	}	
-	
-	public String getPicasaSentiment() {
-		return getExternalAccountSentiment(ExternalAccountType.PICASA);
-	}
-	
-	public String getPicasaHateQuip() {
-		return getExternalAccountHateQuip(ExternalAccountType.PICASA);
-	}
-	
-	public String getPicasaName() {
-		return getExternalAccountHandle(ExternalAccountType.PICASA);
-	}
-	
-	public String getAmazonSentiment() {
-		return getExternalAccountSentiment(ExternalAccountType.AMAZON);
-	}
-	
-	public String getAmazonHateQuip() {
-		return getExternalAccountHateQuip(ExternalAccountType.AMAZON);
-	}
-	
-	public String getAmazonUrl() {
-		ExternalAccount external;
-		try {
-			external = externalAccounts.lookupExternalAccount(signin.getViewpoint(), signin.getUser(), ExternalAccountType.AMAZON);
-			if (external.hasAccountInfo()) {
-			    return external.getLink();
-			}
-		} catch (NotFoundException e) {
-			// nothing to do
-		}
-		return "";
-	}
-	
 	/**
 	 * Returns a list of supported account views; with the ExternalAccount information for the
 	 * user filled in for the account types for which the user has accounts.
 	 */
 	public ListBean<ExternalAccountView> getSupportedAccounts() {
+		if (supportedAccountsListBean != null) {
+			return supportedAccountsListBean;
+		}
+			
 		List<ExternalAccountView> supportedAccounts = new ArrayList<ExternalAccountView>(); 
 		for (ExternalAccountType type : ExternalAccountType.alphabetizedValues()) {
 			if (type.isSupported()) {
-				try {
-				    ExternalAccount externalAccount = 
-				    	externalAccounts.lookupExternalAccount(signin.getViewpoint(), signin.getUser(), type);
-				    supportedAccounts.add(new ExternalAccountView(externalAccount));
-				} catch (NotFoundException e) {
-					supportedAccounts.add(new ExternalAccountView(type));
-				}
+	            OnlineAccountType onlineAccountType = externalAccounts.getOnlineAccountType(type);
+	            	  
+	            Set<ExternalAccount> externalAccountsSet = 
+			        externalAccounts.lookupExternalAccounts(signin.getViewpoint(), signin.getUser(), onlineAccountType);
+			
+	            ExternalAccountView indifferentAccount = null;
+	            boolean foundLovedOrHatedAccount = false;
+			    for (ExternalAccount externalAccount : externalAccountsSet) {
+			    	// normally we shouldn't have some accounts that are hated and some that are loved of the same time,
+			    	// but we don't have to check for it here
+			    	if (externalAccount.getSentiment() == Sentiment.LOVE || externalAccount.getSentiment() == Sentiment.HATE) {
+					    supportedAccounts.add(new ExternalAccountView(externalAccount));
+					    foundLovedOrHatedAccount = true;
+			    	} else {
+			    		indifferentAccount = new ExternalAccountView(externalAccount);
+			    	}		    	    
+			    }
+			    
+			    // we should only allow the user to edit one indifferent account at a time; since we never delete accounts, this
+			    // is a way to allow reusing accounts
+			    if (!foundLovedOrHatedAccount && indifferentAccount != null) {
+			    	supportedAccounts.add(indifferentAccount);
+			    }
+			    
+			    if (externalAccountsSet.isEmpty()) {    
+				    supportedAccounts.add(new ExternalAccountView(onlineAccountType));
+			    }
 			}
 		}
-		return new ListBean<ExternalAccountView>(supportedAccounts);
+		
+		supportedAccountsListBean = new ListBean<ExternalAccountView>(supportedAccounts);
+		return supportedAccountsListBean;
 	}
 
 	public ListBean<ExternalAccountView> getGnomeSupportedAccounts() {
+		if (gnomeSupportedAccountsListBean != null) {
+			return gnomeSupportedAccountsListBean;
+		}
+			
 		List<ExternalAccountView> supportedAccounts = new ArrayList<ExternalAccountView>(); 
-		for (OnlineAccountType type : externalAccounts.getAllOnlineAccountTypes()) {
-			// this takes advantage of the fact that all online account types currently have corresponding
-			// external accounts, but we'll need to change this soon
-			if (type.isSupported() && type.getAccountType() != null) {
-				try {
-				    ExternalAccount externalAccount = 
-				    	externalAccounts.lookupExternalAccount(signin.getViewpoint(), signin.getUser(), type.getAccountType());
-				    supportedAccounts.add(new ExternalAccountView(externalAccount));
-				} catch (NotFoundException e) {
-					supportedAccounts.add(new ExternalAccountView(type.getAccountType()));
-				}
+		List<OnlineAccountType> allTypes = externalAccounts.getAllOnlineAccountTypes();
+		List<OnlineAccountType> alphabetizedTypes =
+			SortUtils.sortCollection(allTypes.toArray(new OnlineAccountType[allTypes.size()]), "getFullName");
+		
+		for (OnlineAccountType type : alphabetizedTypes) {
+			if (type.isSupported()) {
+			    Set<ExternalAccount> externalAccountsSet = 
+			    	externalAccounts.lookupExternalAccounts(signin.getViewpoint(), signin.getUser(), type);
+
+	            ExternalAccountView indifferentAccount = null;
+	            boolean foundLovedOrHatedAccount = false;
+			    for (ExternalAccount externalAccount : externalAccountsSet) {
+			    	// normally we shouldn't have some accounts that are hated and some that are loved of the same time,
+			    	// but we don't have to check for it here
+			    	if (externalAccount.getSentiment() == Sentiment.LOVE || externalAccount.getSentiment() == Sentiment.HATE) {
+					    supportedAccounts.add(new ExternalAccountView(externalAccount));
+					    foundLovedOrHatedAccount = true;
+			    	} else {
+			    		indifferentAccount = new ExternalAccountView(externalAccount);
+			    	}		    	    
+			    }
+			    
+			    // we should only allow the user to edit one indifferent account at a time; since we never delete accounts, this
+			    // is a way to allow reusing accounts
+			    if (!foundLovedOrHatedAccount && indifferentAccount != null) {
+			    	supportedAccounts.add(indifferentAccount);		    
+			    }
+			    
+			    if (externalAccountsSet.isEmpty()) {    
+				    supportedAccounts.add(new ExternalAccountView(type));
+			    }
 			}
 		}
-		return new ListBean<ExternalAccountView>(supportedAccounts);
+		
+		gnomeSupportedAccountsListBean = new ListBean<ExternalAccountView>(supportedAccounts);
+		return gnomeSupportedAccountsListBean;
 	}
 	
 	/**
 	 * Returns names and links for user's Amazon reviews page and public wish lists.
 	 */
 	public List<Pair<String, String>> getAmazonLinks() {
-		if (!getAmazonSentiment().equalsIgnoreCase(Sentiment.LOVE.name()))
+		if (!getExternalAccountSentiment(ExternalAccountType.AMAZON).equalsIgnoreCase(Sentiment.LOVE.name()))
 			return null;
 		
 		String amazonUserId = getExternalAccountHandle(ExternalAccountType.AMAZON);

Modified: dumbhippo/trunk/server/src/com/dumbhippo/web/servlets/FacebookServlet.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/web/servlets/FacebookServlet.java	2008-09-15 16:15:39 UTC (rev 7507)
+++ dumbhippo/trunk/server/src/com/dumbhippo/web/servlets/FacebookServlet.java	2008-09-20 00:31:07 UTC (rev 7508)
@@ -261,14 +261,14 @@
 					    		XPath xpath = XPathFactory.newInstance().newXPath();
 					    		String nsid = ((Node)xpath.evaluate("/result/flickrUser/nsid", doc, XPathConstants.NODE)).getTextContent();
 					    		logger.debug("Got nsid {} when setting Flickr account", nsid);
-					    		httpMethods.doSetFlickrAccount(new XmlBuilder(), userViewpoint, nsid, entryValue);
+					    		httpMethods.doSetFlickrAccount(new XmlBuilder(), userViewpoint, "mugshot", nsid, entryValue);
 					    		accountsSetSuccessful.add(ExternalAccountType.FLICKR);
 					    	} else {
 					    		Method setAccount = httpMethods.getClass().getMethod("doSet" + entry.getKey().getDomNodeIdName() + "Account",
 					    				                                             new Class[] {XmlBuilder.class, UserViewpoint.class, String.class});	
 					    		XmlBuilder resultXml = new XmlBuilder();
 					    		resultXml.openElement("result");
-					    		setAccount.invoke(httpMethods, new Object[] {resultXml, userViewpoint, entryValue});
+					    		setAccount.invoke(httpMethods, new Object[] {resultXml, userViewpoint, "mugshot", entryValue});
 					    		resultXml.closeElement(); // result
 					    		// we have messages telling the user about certain limitations of their account
 					    		// for MySpace, Twitter, Reddit, and Amazon
@@ -428,7 +428,7 @@
 				    }
 				    
 				    xml.openElement("div", "style", "color:#666666;width:275px;");			    
-				    if (externalAccount.isInfoTypeProvidedBySite()) {
+				    if (externalAccount.getExternalAccountType().isInfoTypeProvidedBySite()) {
 				        xml.append("Enter your ");
 				        if (externalAccount.getExternalAccountType() == ExternalAccountType.BLOG) {
 				        	xml.append(" blog (e.g. ");
@@ -445,7 +445,7 @@
 				        	  	               "href", externalAccount.getExternalAccountType().getSiteLink(), 
 				        		               "target", "_blank");
 				        }    
-				        xml.append(" " + externalAccount.getSiteUserInfoType());
+				        xml.append(" " + externalAccount.getExternalAccountType().getSiteUserInfoType());
 					    if (externalAccount.getExternalAccountType().getHelpUrl().trim().length() > 0) {
 					    	xml.append(" (");
 					        xml.appendTextNode("a", "help me find it", 
@@ -455,7 +455,7 @@
 					    }
 					    xml.append(".");
 				    } else {
-				        xml.append("Enter your " + externalAccount.getSiteUserInfoType() + " ");
+				        xml.append("Enter your " + externalAccount.getExternalAccountType().getSiteUserInfoType() + " ");
 				        xml.appendTextNode("a", externalAccount.getSiteName(), 
 				        		           "href", externalAccount.getExternalAccountType().getSiteLink(), 
 				        		           "target", "_blank");
@@ -554,10 +554,10 @@
 					    	externalAccounts.lookupExternalAccount(new UserViewpoint(user, Site.MUGSHOT), user, type);
 					    supportedAccounts.add(new ExternalAccountView(externalAccount));
 					} catch (NotFoundException e) {
-						supportedAccounts.add(new ExternalAccountView(type));
+						supportedAccounts.add(new ExternalAccountView(externalAccounts.getOnlineAccountType(type)));
 					}
 				} else {
-					supportedAccounts.add(new ExternalAccountView(type));
+					supportedAccounts.add(new ExternalAccountView(externalAccounts.getOnlineAccountType(type)));
 				}
 			}
 		}

Modified: dumbhippo/trunk/server/web/javascript/dh/account.js
===================================================================
--- dumbhippo/trunk/server/web/javascript/dh/account.js	2008-09-15 16:15:39 UTC (rev 7507)
+++ dumbhippo/trunk/server/web/javascript/dh/account.js	2008-09-20 00:31:07 UTC (rev 7508)
@@ -10,6 +10,51 @@
 dojo.require("dh.dom");
 dojo.require("dh.event");
 
+
+dh.account.hateQuipsArray = [];
+dh.account.hateQuipsArray['myspace'] = 'I despise Tom and his space';
+dh.account.hateQuipsArray['youtube'] = 'Video should kill the internet geeks';
+dh.account.hateQuipsArray['flickr'] = 'Flickr doesn\'t do it for me';
+dh.account.hateQuipsArray['linkedin'] = 'LinkedIn is for nerds';
+dh.account.hateQuipsArray['rhapsody_rss'] = 'All-you-can-eat music services hurt my diet';
+dh.account.hateQuipsArray['lastfm'] = 'Uhh...what\'s Last.fm?';
+dh.account.hateQuipsArray['delicious'] = 'del.icio.us isn\'t';
+dh.account.hateQuipsArray['twitter'] = 'And *why* do I care what you\'re doing?';
+dh.account.hateQuipsArray['digg'] = 'I don\'t dig it';
+dh.account.hateQuipsArray['reddit'] = 'Not reading it';
+dh.account.hateQuipsArray['netflix_rss'] = 'Movie rental stores are my daily respite';
+dh.account.hateQuipsArray['google_reader_rss'] = 'I don\'t like to read';
+dh.account.hateQuipsArray['picasa'] = 'Pictures of cats';
+dh.account.hateQuipsArray['amazon'] = 'I enjoy an ascetic lifestyle';
+    
+dh.account.whatWillHappenArray = [];
+dh.account.whatWillHappenArray['myspace'] = 'Your friends get updates when you post to your MySpace blog.';
+dh.account.whatWillHappenArray['youtube'] = 'Your friends get updates when you upload new videos.';
+dh.account.whatWillHappenArray['flickr'] = 'Your friends get updates when you upload new photos and photo sets.';
+dh.account.whatWillHappenArray['linkedin'] = '';
+dh.account.whatWillHappenArray['rhapsody_rss'] = 'Your friends will see updates from your Rhapsody playlist.';
+dh.account.whatWillHappenArray['lastfm'] = 'Your friends see what music you\'re listening to.';
+dh.account.whatWillHappenArray['delicious'] = 'Your friends get updates when you add public bookmarks.';
+dh.account.whatWillHappenArray['twitter'] = 'Your friends see your Twitter updates.';
+dh.account.whatWillHappenArray['digg'] = 'Your friends get updates when you add diggs.';
+dh.account.whatWillHappenArray['reddit'] = 'Your friends get updates when you rate sites.';
+dh.account.whatWillHappenArray['netflix_rss'] = 'Your friends get updates when you are sent new movies.';
+dh.account.whatWillHappenArray['google_reader_rss'] = 'Your friends see your Google Reader public shared items.';
+dh.account.whatWillHappenArray['picasa'] = 'Your friends see your public Picasa albums.';
+dh.account.whatWillHappenArray['amazon'] = 'Your friends see what you add to your public wish lists and your reviews.';
+        
+dh.account.helpLinkArray = [];
+dh.account.helpLinkArray['rhapsody_rss'] = 'http://www.rhapsody.com/myrhapsody/rss.html';
+dh.account.helpLinkArray['netflix_rss'] = 'http://www.netflix.com/RSSFeeds';
+dh.account.helpLinkArray['google_reader_rss'] = 'http://www.google.com/reader/view';
+dh.account.helpLinkArray['amazon'] ='http://www.amazon.com/gp/pdp/profile/';
+        
+dh.account.specialLoveValuesArray = [];
+dh.account.specialLoveValuesArray['rhapsody_rss'] = 'My Recently Played Tracks'; 
+dh.account.specialLoveValuesArray['netflix_rss'] = 'My Movies At Home';
+dh.account.specialLoveValuesArray['google_reader_rss'] = 'My Shared Items';
+dh.account.specialLoveValuesArray['amazon'] = 'My Profile';
+   
 dh.account.generatingRandomBio = false;
 dh.account.generateRandomBio = function() {
 	if (dh.account.generatingRandomBio) {
@@ -176,9 +221,9 @@
 					loadFunc, errorFunc);
 }
 
-dh.account.removeExternalAccount = function(type, loadFunc, errorFunc) {
+dh.account.removeExternalAccount = function(id, loadFunc, errorFunc) {
    	dh.server.doXmlMethod("removeexternalaccount",
-				     { "type" : type },
+				     { "id" : id },
 						loadFunc, errorFunc);
 }
 dh.account.findFlickrAccount = function(email, loadFunc, errorFunc) {
@@ -187,87 +232,144 @@
 						loadFunc, errorFunc);
 }
 
-dh.account.setFlickrAccount = function(nsid, email, loadFunc, errorFunc) {
+dh.account.setFlickrAccount = function(id, nsid, email, loadFunc, errorFunc) {
    	dh.server.doXmlMethod("setflickraccount",
-				     { "nsid" : nsid, "email" : email },
+				     { "id" : id, "nsid" : nsid, "email" : email },
 				     	loadFunc, errorFunc);
 }
-dh.account.setLinkedInProfile = function(name, loadFunc, errorFunc) {
+dh.account.setLinkedInProfile = function(id, name, loadFunc, errorFunc) {
    	dh.server.doXmlMethod("setlinkedinaccount",
-				     { "urlOrName" : name },
+				     { "id" : id, "urlOrName" : name },
 						loadFunc, errorFunc);
 }
-dh.account.setMyspaceName = function(name, loadFunc, errorFunc) {
+dh.account.setMyspaceName = function(id, name, loadFunc, errorFunc) {
    	dh.server.doXmlMethod("setmyspaceaccount",
-				     { "name" : name },
+				     { "id" : id, "name" : name },
 						loadFunc, errorFunc);
 }
-dh.account.setYouTubeName = function(name, loadFunc, errorFunc) {
+dh.account.setYouTubeName = function(id, name, loadFunc, errorFunc) {
    	dh.server.doXmlMethod("setyoutubeaccount",
-				     { "urlOrName" : name },
+				     { "id" : id, "urlOrName" : name },
 						loadFunc, errorFunc);
 }
-dh.account.setLastFmName = function(name, loadFunc, errorFunc) {
+dh.account.setLastFmName = function(id, name, loadFunc, errorFunc) {
    	dh.server.doXmlMethod("setlastfmaccount",
-				     { "urlOrName" : name },
+				     { "id" : id, "urlOrName" : name },
 						loadFunc, errorFunc);
 }
-dh.account.setDeliciousName = function(name, loadFunc, errorFunc) {
+dh.account.setDeliciousName = function(id, name, loadFunc, errorFunc) {
    	dh.server.doXmlMethod("setdeliciousaccount",
-				     { "urlOrName" : name },
+				     { "id" : id, "urlOrName" : name },
 						loadFunc, errorFunc);
 }
-dh.account.setTwitterName = function(name, loadFunc, errorFunc) {
+dh.account.setTwitterName = function(id, name, loadFunc, errorFunc) {
    	dh.server.doXmlMethod("settwitteraccount",
-				     { "urlOrName" : name },
+				     { "id" : id, "urlOrName" : name },
 						loadFunc, errorFunc);
 }
-dh.account.setDiggName = function(name, loadFunc, errorFunc) {
+dh.account.setDiggName = function(id, name, loadFunc, errorFunc) {
    	dh.server.doXmlMethod("setdiggaccount",
-				     { "urlOrName" : name },
+				     { "id" : id, "urlOrName" : name },
 						loadFunc, errorFunc);
 }
-dh.account.setRedditName = function(name, loadFunc, errorFunc) {
+dh.account.setRedditName = function(id, name, loadFunc, errorFunc) {
    	dh.server.doXmlMethod("setredditaccount",
-				     { "urlOrName" : name },
+				     { "id" : id, "urlOrName" : name },
 						loadFunc, errorFunc);
 }
-dh.account.setRhapsodyUrl = function(name, loadFunc, errorFunc) {
+dh.account.setRhapsodyUrl = function(id, name, loadFunc, errorFunc) {
    	dh.server.doXmlMethod("setrhapsodyaccount",
-   	                      { "url" : name },
+   	                      { "id" : id, "url" : name },
    	                      loadFunc, errorFunc);
 }
-dh.account.setNetflixUrl = function(name, loadFunc, errorFunc) {
+dh.account.setNetflixUrl = function(id, name, loadFunc, errorFunc) {
    	dh.server.doXmlMethod("setnetflixaccount",
-   	                      { "url" : name },
+   	                      { "id" : id, "url" : name },
    	                      loadFunc, errorFunc);
 }
  
-dh.account.setGoogleReaderUrl = function(name, loadFunc, errorFunc) {
+dh.account.setGoogleReaderUrl = function(id, name, loadFunc, errorFunc) {
    	dh.server.doXmlMethod("setgooglereaderaccount",
-   	                      { "url" : name },
+   	                      { "id" : id, "url" : name },
    	                      loadFunc, errorFunc);
 }
-dh.account.setPicasaName = function(name, loadFunc, errorFunc) {
+dh.account.setPicasaName = function(id, name, loadFunc, errorFunc) {
    	dh.server.doXmlMethod("setpicasaaccount",
-				     { "urlOrName" : name },
+				     { "id" : id, "urlOrName" : name },
 						loadFunc, errorFunc);
 }
 
-dh.account.setAmazonUrl = function(name, loadFunc, errorFunc) {
+dh.account.setAmazonUrl = function(id, name, loadFunc, errorFunc) {
    	dh.server.doXmlMethod("setamazonaccount",
-				          { "urlOrUserId" : name },
+				          { "id" : id, "urlOrUserId" : name },
 						  loadFunc, errorFunc);
 }
 
-dh.account.createExternalAccountOnHateSavedFunc = function(entry, accountType) {
+dh.account.setOnlineAccountValue = function(type, id, value, loadFunc, errorFunc) {
+   	dh.server.doXmlMethod("setonlineaccountvalue",
+				          { "type" : type, "id" : id, "value" : value },
+						  loadFunc, errorFunc);
+}
+
+dh.account.createExternalAccountOnLoveSavedFunc = function(entry, accountType, id, mugshotEnabled) {
 	return function(value) {
+	    switch(accountType) {
+		case 'myspace':
+		    dh.account.onMyspaceLoveSaved(entry, id, value);
+		    break;
+        case 'youtube':
+            dh.account.onYouTubeLoveSaved(entry, id, value);
+		    break;
+        case 'flickr':
+            dh.account.onFlickrLoveSaved(entry, id, value);
+            break;
+        case 'linkedin':
+            dh.account.onLinkedInLoveSaved(entry, id, value);
+		    break;
+        case 'rhapsody_rss':
+            dh.account.onRhapsodyLoveSaved(entry, id, value);
+		    break;
+        case 'lastfm':
+            dh.account.onLastFmLoveSaved(entry, id, value);
+		    break;
+        case 'delicious':
+            dh.account.onDeliciousLoveSaved(entry, id, value);
+            break;
+        case 'twitter':
+            dh.account.onTwitterLoveSaved(entry, id, value);
+            break;
+        case 'digg':
+            dh.account.onDiggLoveSaved(entry, id, value);
+		    break;            
+        case 'reddit':
+            dh.account.onRedditLoveSaved(entry, id, value);
+		    break;            
+        case 'netflix_rss':
+            dh.account.onNetflixLoveSaved(entry, id, value);
+		    break;            
+        case 'google_reader_rss':
+            dh.account.onGoogleReaderLoveSaved(entry, id, value);
+		    break;            
+        case 'picasa':
+            dh.account.onPicasaLoveSaved(entry, id, value);
+		    break;            
+        case 'amazon':
+            dh.account.onAmazonLoveSaved(entry, id, value);
+            break;
+		default:
+			dh.account.onLoveSaved(entry, accountType, id, value);
+		}
+	}
+}
+
+dh.account.createExternalAccountOnHateSavedFunc = function(entry, accountType, id, mugshotEnabled) {
+	return function(value) {
 		var oldMode = entry.getMode();
 		entry.setBusy();
 		dh.account.hateExternalAccount(accountType, value,
 		 	    	 function(childNodes, http) {
 		 	    	 	entry.setMode('hate');
-		 	    	 	if (accountType == 'AMAZON') {
+		 	    	 	if (mugshotEnabled && accountType == 'AMAZON') {
 	 	    	            var amazonDetailsNodes = dh.html.getElementsByClass('dh-amazon-details');
 	                        var i = 0;
 	                        for (i = 0; i < amazonDetailsNodes.length; ++i) {
@@ -283,14 +385,14 @@
 	}
 }
 
-dh.account.createExternalAccountOnCanceledFunc = function(entry, accountType) {
+dh.account.createExternalAccountOnCanceledFunc = function(entry, accountType, id, mugshotEnabled) {
 	return function(value) {
 		var oldMode = entry.getMode();
 		entry.setBusy();
-		dh.account.removeExternalAccount(accountType, 
+		dh.account.removeExternalAccount(id, 
 		 	    	 function(childNodes, http) {
 		 	    	 	entry.setMode('indifferent');
-		 	    	 	if (accountType == 'AMAZON') {
+		 	    	 	if (mugshotEnabled && accountType == 'amazon') {
 	 	    	            var amazonDetailsNodes = dh.html.getElementsByClass('dh-amazon-details');
 	                        var i = 0;
 	                        for (i = 0; i < amazonDetailsNodes.length; ++i) {
@@ -306,8 +408,7 @@
 	}
 }
 
-dh.account.onFlickrLoveSaved = function(value) {
-	var entry = dh.account.flickrEntry;
+dh.account.onFlickrLoveSaved = function(entry, value, id) {
 	var oldMode = entry.getMode();
 	entry.setBusy();
 	dh.account.findFlickrAccount(value,
@@ -330,7 +431,7 @@
 					}
 				}
 				
-				dh.account.setFlickrAccount(nsid, value,
+				dh.account.setFlickrAccount(id, nsid, value,
 					function(childNodes, http) {
 						entry.setMode('love');
 					},
@@ -345,11 +446,10 @@
 	  	    });
 }
 
-dh.account.onMyspaceLoveSaved = function(value) {
-	var entry = dh.account.myspaceEntry;
+dh.account.onMyspaceLoveSaved = function(entry, id, value) {
 	var oldMode = entry.getMode();
 	entry.setBusy();
-  	dh.account.setMyspaceName(value, 
+  	dh.account.setMyspaceName(id, value, 
 	 	    	 function(childNodes, http) {
 	 	    	     var i = 0;
 					 for (i = 0; i < childNodes.length; ++i) {
@@ -371,11 +471,10 @@
 	  	    	 }); 
 }
 
-dh.account.onYouTubeLoveSaved = function(value) {
-	var entry = dh.account.youTubeEntry;
+dh.account.onYouTubeLoveSaved = function(entry, id, value) {
 	var oldMode = entry.getMode();
 	entry.setBusy();
-  	dh.account.setYouTubeName(value, 
+  	dh.account.setYouTubeName(id, value, 
 	 	    	 function(childNodes, http) {
 	 	    	 	entry.setMode('love');
 	  	    	 },
@@ -385,11 +484,10 @@
 	  	    	 }); 
 }
 
-dh.account.onLastFmLoveSaved = function(value) {
-	var entry = dh.account.lastFmEntry;
+dh.account.onLastFmLoveSaved = function(entry, id, value) {
 	var oldMode = entry.getMode();
 	entry.setBusy();
-  	dh.account.setLastFmName(value, 
+  	dh.account.setLastFmName(id, value, 
 	 	    	 function(childNodes, http) {
 	 	    	 	entry.setError(null);
 	 	    	 	entry.setMode('love');
@@ -400,11 +498,10 @@
 	  	    	 }); 
 }
 
-dh.account.onLinkedInLoveSaved = function(value) {
-	var entry = dh.account.linkedInEntry;
+dh.account.onLinkedInLoveSaved = function(entry, id, value) {
 	var oldMode = entry.getMode();
 	entry.setBusy();
-  	dh.account.setLinkedInProfile(value,
+  	dh.account.setLinkedInProfile(id, value,
 	 	    	 function(childNodes, http) {
 				    var username = null;
 					var i = 0;
@@ -426,11 +523,10 @@
 	  	    	 }); 
 }
 
-dh.account.onRhapsodyLoveSaved = function(value) {
-	var entry = dh.account.rhapsodyEntry;
+dh.account.onRhapsodyLoveSaved = function(entry, id, value) {
 	var oldMode = entry.getMode();
 	entry.setBusy();
-  	dh.account.setRhapsodyUrl(value, 
+  	dh.account.setRhapsodyUrl(id, value, 
 	 	    	 function(childNodes, http) {
 	 	    	 	entry.setMode('love');
 	  	    	 },
@@ -440,11 +536,10 @@
 	  	    	 }); 
 }
 
-dh.account.onDeliciousLoveSaved = function(value) {
-	var entry = dh.account.deliciousEntry;
+dh.account.onDeliciousLoveSaved = function(entry, id, value) {
 	var oldMode = entry.getMode();
 	entry.setBusy();
-  	dh.account.setDeliciousName(value,
+  	dh.account.setDeliciousName(id, value,
 	 	    	 function(childNodes, http) {
 				    var username = null;
 					var i = 0;
@@ -466,11 +561,10 @@
 	  	    	 }); 
 }
 
-dh.account.onTwitterLoveSaved = function(value) {
-	var entry = dh.account.twitterEntry;
+dh.account.onTwitterLoveSaved = function(entry, id, value) {
 	var oldMode = entry.getMode();
 	entry.setBusy();
-  	dh.account.setTwitterName(value,
+  	dh.account.setTwitterName(id, value,
 	 	    	 function(childNodes, http) {
 				    var username = null;
 					var i = 0;
@@ -497,11 +591,10 @@
 	  	    	 }); 
 }
 
-dh.account.onDiggLoveSaved = function(value) {
-	var entry = dh.account.diggEntry;
+dh.account.onDiggLoveSaved = function(entry, id, value) {
 	var oldMode = entry.getMode();
 	entry.setBusy();
-  	dh.account.setDiggName(value,
+  	dh.account.setDiggName(id, value,
 	 	    	 function(childNodes, http) {
 				    var username = null;
 					var i = 0;
@@ -523,11 +616,10 @@
 	  	    	 }); 
 }
 
-dh.account.onRedditLoveSaved = function(value) {
-	var entry = dh.account.redditEntry;
+dh.account.onRedditLoveSaved = function(entry, id, value) {
 	var oldMode = entry.getMode();
 	entry.setBusy();
-  	dh.account.setRedditName(value,
+  	dh.account.setRedditName(id, value,
 	 	    	 function(childNodes, http) {
 				    var username = null;
 					var i = 0;
@@ -554,11 +646,10 @@
 	  	    	 }); 
 }
 
-dh.account.onNetflixLoveSaved = function(value) {
-	var entry = dh.account.netflixEntry;
+dh.account.onNetflixLoveSaved = function(entry, id, value) {
 	var oldMode = entry.getMode();
 	entry.setBusy();
-  	dh.account.setNetflixUrl(value, 
+  	dh.account.setNetflixUrl(id, value, 
 	 	    	 function(childNodes, http) {
 	 	    	 	entry.setMode('love');
 	  	    	 },
@@ -568,11 +659,10 @@
 	  	    	 }); 
 }
 
-dh.account.onGoogleReaderLoveSaved = function(value) {
-	var entry = dh.account.googleReaderEntry;
+dh.account.onGoogleReaderLoveSaved = function(entry, id, value) {
 	var oldMode = entry.getMode();
 	entry.setBusy();
-  	dh.account.setGoogleReaderUrl(value, 
+  	dh.account.setGoogleReaderUrl(id, value, 
 	 	    	 function(childNodes, http) {
 	 	    	 	entry.setMode('love');
 	  	    	 },
@@ -582,11 +672,10 @@
 	  	    	 }); 
 }
 
-dh.account.onPicasaLoveSaved = function(value) {
-	var entry = dh.account.picasaEntry;
+dh.account.onPicasaLoveSaved = function(entry, id, value) {
 	var oldMode = entry.getMode();
 	entry.setBusy();
-  	dh.account.setPicasaName(value,
+  	dh.account.setPicasaName(id, value,
 	 	    	 function(childNodes, http) {
 				    var username = null;
 					var i = 0;
@@ -608,11 +697,10 @@
 	  	    	 }); 
 }
 
-dh.account.onAmazonLoveSaved = function(value) {
-	var entry = dh.account.amazonEntry;
+dh.account.onAmazonLoveSaved = function(entry, id, value) {
 	var oldMode = entry.getMode();
 	entry.setBusy();
-  	dh.account.setAmazonUrl(value, 
+  	dh.account.setAmazonUrl(id, value, 
 	 	    	 function(childNodes, http) {
 	 	    	 	entry.setMode('love');
 	 	    	    var amazonDetailsNodes = dh.html.getElementsByClass('dh-amazon-details');
@@ -670,6 +758,19 @@
 	  	    	 }); 
 }
 
+dh.account.onLoveSaved = function(entry, type, id, value) {
+	var oldMode = entry.getMode();
+	entry.setBusy();
+  	dh.account.setOnlineAccountValue(type, id, value, 
+	 	    	 function(childNodes, http) {
+	 	    	 	entry.setMode('love');
+	  	    	 },
+	  	    	 function(code, msg, http) {
+	  	    	 	alert(msg);
+	  	    	 	entry.setMode(oldMode);
+	  	    	 }); 
+}
+
 dh.account.aimVerify = function() {
 	dh.server.getTextGET("aimVerifyLink", 
 						{ },
@@ -698,134 +799,6 @@
 					 });    
 }
 
-dh.account.createMyspaceEntry = function() {
-    dh.account.myspaceEntry = new dh.lovehate.Entry('dhMySpace', 'Myspace username', dh.account.initialMyspaceName,
-							'I despise Tom and his space', dh.account.initialMyspaceHateQuip, 'Your friends get updates when you post to your MySpace blog.');
-	dh.account.myspaceEntry.onLoveSaved = dh.account.onMyspaceLoveSaved;
-	dh.account.myspaceEntry.onHateSaved = dh.account.createExternalAccountOnHateSavedFunc(dh.account.myspaceEntry, 'MYSPACE');
-	dh.account.myspaceEntry.onCanceled = dh.account.createExternalAccountOnCanceledFunc(dh.account.myspaceEntry, 'MYSPACE');
-}
-
-dh.account.createYouTubeEntry = function() {
-    dh.account.youTubeEntry = new dh.lovehate.Entry('dhYouTube', 'YouTube username or profile URL', dh.account.initialYouTubeName,
-							'Video should kill the internet geeks', dh.account.initialYouTubeHateQuip, 'Your friends get updates when you upload new videos.');
-	dh.account.youTubeEntry.onLoveSaved = dh.account.onYouTubeLoveSaved;
-	dh.account.youTubeEntry.onHateSaved = dh.account.createExternalAccountOnHateSavedFunc(dh.account.youTubeEntry, 'YOUTUBE');
-	dh.account.youTubeEntry.onCanceled = dh.account.createExternalAccountOnCanceledFunc(dh.account.youTubeEntry, 'YOUTUBE');
-}
-
-dh.account.createLastFmEntry = function() {	
-	dh.account.lastFmEntry = new dh.lovehate.Entry('dhLastFm', 'Last.fm username', dh.account.initialLastFmName,
-					'Uhh...what\'s Last.fm?', dh.account.initialLastFmHateQuip, 'Your friends see what music you\'re listening to.');
-	dh.account.lastFmEntry.onLoveSaved = dh.account.onLastFmLoveSaved;
-	dh.account.lastFmEntry.onHateSaved = dh.account.createExternalAccountOnHateSavedFunc(dh.account.lastFmEntry, 'LASTFM');
-	dh.account.lastFmEntry.onCanceled = dh.account.createExternalAccountOnCanceledFunc(dh.account.lastFmEntry, 'LASTFM');		
-}
-
-dh.account.createFlickrEntry = function() {		
-	dh.account.flickrEntry = new dh.lovehate.Entry('dhFlickr', 'Email used for Flickr account', dh.account.initialFlickrEmail,
-					'Flickr doesn\'t do it for me', dh.account.initialFlickrHateQuip, 'Your friends get updates when you upload new photos and photo sets.');
-	dh.account.flickrEntry.onLoveSaved = dh.account.onFlickrLoveSaved;
-	dh.account.flickrEntry.onHateSaved = dh.account.createExternalAccountOnHateSavedFunc(dh.account.flickrEntry, 'FLICKR');
-	dh.account.flickrEntry.onCanceled = dh.account.createExternalAccountOnCanceledFunc(dh.account.flickrEntry, 'FLICKR');
-}
-
-dh.account.createLinkedInEntry = function() {	
-	dh.account.linkedInEntry = new dh.lovehate.Entry('dhLinkedIn', 'LinkedIn username or profile URL', dh.account.initialLinkedInName,
-					'LinkedIn is for nerds', dh.account.initialLinkedInHateQuip);
-	dh.account.linkedInEntry.onLoveSaved = dh.account.onLinkedInLoveSaved;
-	dh.account.linkedInEntry.onHateSaved = dh.account.createExternalAccountOnHateSavedFunc(dh.account.linkedInEntry, 'LINKED_IN');
-	dh.account.linkedInEntry.onCanceled = dh.account.createExternalAccountOnCanceledFunc(dh.account.linkedInEntry, 'LINKED_IN');	
-}
-
-dh.account.createRhapsodyEntry = function() {	
-	dh.account.rhapsodyEntry = new dh.lovehate.Entry('dhRhapsody', 'Rhapsody \u201CRecently Played Tracks\u201D RSS feed URL', dh.account.initialRhapsodyUrl,
-					'All-you-can-eat music services hurt my diet', dh.account.initialRhapsodyHateQuip, 
-					'Your friends will see updates from your Rhapsody playlist.',
-                    'http://www.rhapsody.com/myrhapsody/rss.html');
-	dh.account.rhapsodyEntry.setSpecialLoveValue("My Recently Played Tracks");				
-	dh.account.rhapsodyEntry.onLoveSaved = dh.account.onRhapsodyLoveSaved;
-	dh.account.rhapsodyEntry.onHateSaved = dh.account.createExternalAccountOnHateSavedFunc(dh.account.rhapsodyEntry, 'RHAPSODY');
-	dh.account.rhapsodyEntry.onCanceled = dh.account.createExternalAccountOnCanceledFunc(dh.account.rhapsodyEntry, 'RHAPSODY');	
-}
-
-dh.account.createDeliciousEntry = function() {	
-	dh.account.deliciousEntry = new dh.lovehate.Entry('dhDelicious', 'del.icio.us username or profile URL', dh.account.initialDeliciousName,
-					'del.icio.us isn\'t', dh.account.initialDeliciousHateQuip, 'Your friends get updates when you add public bookmarks.');
-	dh.account.deliciousEntry.onLoveSaved = dh.account.onDeliciousLoveSaved;
-	dh.account.deliciousEntry.onHateSaved = dh.account.createExternalAccountOnHateSavedFunc(dh.account.deliciousEntry, 'DELICIOUS');
-	dh.account.deliciousEntry.onCanceled = dh.account.createExternalAccountOnCanceledFunc(dh.account.deliciousEntry, 'DELICIOUS');
-}
-
-dh.account.createTwitterEntry = function() {	
-	dh.account.twitterEntry = new dh.lovehate.Entry('dhTwitter', 'Twitter username or profile URL', dh.account.initialTwitterName,
-					'And *why* do I care what you\'re doing?', dh.account.initialTwitterHateQuip, 'Your friends see your Twitter updates.');
-	dh.account.twitterEntry.onLoveSaved = dh.account.onTwitterLoveSaved;
-	dh.account.twitterEntry.onHateSaved = dh.account.createExternalAccountOnHateSavedFunc(dh.account.twitterEntry, 'TWITTER');
-	dh.account.twitterEntry.onCanceled = dh.account.createExternalAccountOnCanceledFunc(dh.account.twitterEntry, 'TWITTER');
-}
-
-dh.account.createGnomeTwitterEntry = function() {	
-	dh.account.twitterEntry = new dh.love.Entry('dhTwitter', 'Twitter username or profile URL', dh.account.initialTwitterName);
-	dh.account.twitterEntry.onLoveSaved = dh.account.onTwitterLoveSaved;
-	dh.account.twitterEntry.onCanceled = dh.account.createExternalAccountOnCanceledFunc(dh.account.twitterEntry, 'TWITTER');
-}
-
-dh.account.createDiggEntry = function() {	
-	dh.account.diggEntry = new dh.lovehate.Entry('dhDigg', 'Digg username or profile URL', dh.account.initialDiggName,
-					'I don\'t dig it', dh.account.initialDiggHateQuip, 'Your friends get updates when you add diggs.');
-	dh.account.diggEntry.onLoveSaved = dh.account.onDiggLoveSaved;
-	dh.account.diggEntry.onHateSaved = dh.account.createExternalAccountOnHateSavedFunc(dh.account.diggEntry, 'DIGG');
-	dh.account.diggEntry.onCanceled = dh.account.createExternalAccountOnCanceledFunc(dh.account.diggEntry, 'DIGG');
-}
-
-dh.account.createRedditEntry = function() {	
-	dh.account.redditEntry = new dh.lovehate.Entry('dhReddit', 'Reddit username or profile URL', dh.account.initialRedditName,
-					'Not reading it', dh.account.initialRedditHateQuip, 'Your friends get updates when you rate sites.');
-	dh.account.redditEntry.onLoveSaved = dh.account.onRedditLoveSaved;
-	dh.account.redditEntry.onHateSaved = dh.account.createExternalAccountOnHateSavedFunc(dh.account.redditEntry, 'REDDIT');
-	dh.account.redditEntry.onCanceled = dh.account.createExternalAccountOnCanceledFunc(dh.account.redditEntry, 'REDDIT');
-}
-
-dh.account.createNetflixEntry = function() {	
-	dh.account.netflixEntry = new dh.lovehate.Entry('dhNetflix', 'Netflix \u201CMovies At Home\u201D RSS feed URL', dh.account.initialNetflixUrl,
-					'Movie rental stores are my daily respite', dh.account.initialNetflixHateQuip, 'Your friends get updates when you are sent new movies.',
-					'http://www.netflix.com/RSSFeeds');
-	dh.account.netflixEntry.setSpecialLoveValue("My Movies At Home");	
-	dh.account.netflixEntry.onLoveSaved = dh.account.onNetflixLoveSaved;
-	dh.account.netflixEntry.onHateSaved = dh.account.createExternalAccountOnHateSavedFunc(dh.account.netflixEntry, 'NETFLIX');
-	dh.account.netflixEntry.onCanceled = dh.account.createExternalAccountOnCanceledFunc(dh.account.netflixEntry, 'NETFLIX');
-}
-
-dh.account.createGoogleReaderEntry = function() {	
-	dh.account.googleReaderEntry = new dh.lovehate.Entry('dhGoogleReader', 'Google Reader public shared items page', dh.account.initialGoogleReaderUrl,
-					"I don't like to read", dh.account.initialGoogleReaderHateQuip, 'Your friends see your Google Reader public shared items.',
-					'http://www.google.com/reader/view');
-	dh.account.googleReaderEntry.setSpecialLoveValue("My Shared Items");
-	dh.account.googleReaderEntry.onLoveSaved = dh.account.onGoogleReaderLoveSaved;
-	dh.account.googleReaderEntry.onHateSaved = dh.account.createExternalAccountOnHateSavedFunc(dh.account.googleReaderEntry, 'GOOGLE_READER');
-	dh.account.googleReaderEntry.onCanceled = dh.account.createExternalAccountOnCanceledFunc(dh.account.googleReaderEntry, 'GOOGLE_READER');
-}
-
-dh.account.createPicasaEntry = function() {	
-	dh.account.picasaEntry = new dh.lovehate.Entry('dhPicasa', 'Picasa username or public gallery URL', dh.account.initialPicasaName,
-					'Pictures of cats', dh.account.initialPicasaHateQuip, 'Your friends see your public Picasa albums.');
-	dh.account.picasaEntry.onLoveSaved = dh.account.onPicasaLoveSaved;
-	dh.account.picasaEntry.onHateSaved = dh.account.createExternalAccountOnHateSavedFunc(dh.account.picasaEntry, 'PICASA');
-	dh.account.picasaEntry.onCanceled = dh.account.createExternalAccountOnCanceledFunc(dh.account.picasaEntry, 'PICASA');
-}
-
-dh.account.createAmazonEntry = function() {	
-	dh.account.amazonEntry = new dh.lovehate.Entry('dhAmazon', 'Amazon profile URL', dh.account.initialAmazonUrl,
-					'I enjoy an ascetic lifestyle', dh.account.initialAmazonHateQuip, 
-					'Your friends see what you add to your public wish lists and your reviews.',
-					'http://www.amazon.com/gp/pdp/profile/');
-	dh.account.amazonEntry.setSpecialLoveValue("My Profile");				
-	dh.account.amazonEntry.onLoveSaved = dh.account.onAmazonLoveSaved;
-	dh.account.amazonEntry.onHateSaved = dh.account.createExternalAccountOnHateSavedFunc(dh.account.amazonEntry, 'AMAZON');
-	dh.account.amazonEntry.onCanceled = dh.account.createExternalAccountOnCanceledFunc(dh.account.amazonEntry, 'AMAZON');
-}
-
 dhAccountInit = function() {
 	if (!dh.account.active) {
 	    // we want to disable editing, but still display all the data we have
@@ -879,30 +852,36 @@
 	
     dh.account.createImEntry();
 
+    dh.account.onlineAccountEntries = [];
+    
+    if (dh.account.dhMugshotEnabledFlags != null) {
+	    for (var i = 0; i < dh.account.dhNames.length; ++i) {
+	        if (dh.account.dhNames[i] == "facebook")
+	            continue;
+	            
+	        var onlineAccountEntry = new dh.lovehate.Entry('dh' + dh.account.dhDomIds[i], dh.account.dhUserInfoTypes[i],  dh.account.dhValues[i],
+							                               dh.account.hateQuipsArray[dh.account.dhNames[i]], dh.account.dhHateQuips[i], dh.account.whatWillHappenArray[dh.account.dhNames[i]], 
+							                               dh.account.helpLinkArray[dh.account.dhNames[i]]);
+		
+		    if (dh.account.specialLoveValuesArray[dh.account.dhNames[i]] != null)
+		        onlineAccountEntry.setSpecialLoveValue(dh.account.specialLoveValuesArray[dh.account.dhNames[i]]);
+		    					                                                                                                                             
+	        onlineAccountEntry.onLoveSaved = dh.account.createExternalAccountOnLoveSavedFunc(onlineAccountEntry, dh.account.dhNames[i], dh.account.dhIds[i], dh.account.dhMugshotEnabledFlags[i]);
+	        onlineAccountEntry.onHateSaved = dh.account.createExternalAccountOnHateSavedFunc(onlineAccountEntry,  dh.account.dhNames[i], dh.account.dhIds[i], dh.account.dhMugshotEnabledFlags[i]);
+	        onlineAccountEntry.onCanceled = dh.account.createExternalAccountOnCanceledFunc(onlineAccountEntry, dh.account.dhNames[i], dh.account.dhIds[i], dh.account.dhMugshotEnabledFlags[i]);
+            dh.account.onlineAccountEntries.push(onlineAccountEntry)
+        }  
+    } else {	    
+	    for (var i = 0; i < dh.account.dhNames.length; ++i) {
+	        var onlineAccountEntry = new dh.love.Entry('dh' + dh.account.dhDomIds[i], dh.account.dhUserInfoTypes[i], dh.account.dhValues[i]);
+	        onlineAccountEntry.onLoveSaved = dh.account.createExternalAccountOnLoveSavedFunc(onlineAccountEntry, dh.account.dhNames[i], dh.account.dhIds[i], false);
+	        onlineAccountEntry.onCanceled = dh.account.createExternalAccountOnCanceledFunc(onlineAccountEntry, dh.account.dhNames[i], dh.account.dhIds[i], false);
+            dh.account.onlineAccountEntries.push(onlineAccountEntry);
+        }
+    }
+       
 	dh.photochooser.init("user", dh.account.userId)
-
-	// We assume if one of these exists, they all exist. If the division between the
-	// online.gnome.org and mugshot version of the account page changes, then this needs
-	// to be adjusted
-	if (dh.util.exists('dhMySpaceFormContainer')) {
-	    dh.account.createMyspaceEntry();
-		dh.account.createYouTubeEntry();
-		dh.account.createLastFmEntry();
-		dh.account.createFlickrEntry();
-		dh.account.createLinkedInEntry();
-		dh.account.createRhapsodyEntry();
-		dh.account.createDeliciousEntry();
-		dh.account.createTwitterEntry();
-		dh.account.createDiggEntry();
-		dh.account.createRedditEntry();
-		dh.account.createNetflixEntry();
-		dh.account.createGoogleReaderEntry();
-		dh.account.createPicasaEntry();	
-		dh.account.createAmazonEntry();
-	} else {
-	    // TODO: not sure yet how we'll figure out which entries to create, so hardcoding Twitter for now
-		dh.account.createGnomeTwitterEntry();
-	}	
+	
 }
 
 dh.event.addPageLoadListener(dhAccountInit);

Modified: dumbhippo/trunk/server/web/javascript/dh/lovehate.js
===================================================================
--- dumbhippo/trunk/server/web/javascript/dh/lovehate.js	2008-09-15 16:15:39 UTC (rev 7507)
+++ dumbhippo/trunk/server/web/javascript/dh/lovehate.js	2008-09-20 00:31:07 UTC (rev 7508)
@@ -26,7 +26,7 @@
 	
 	this._loveEntryNode = document.getElementById(baseId + 'LoveEntryId');
 	this._hateEntryNode = document.getElementById(baseId + 'HateEntryId');
-	
+	 
 	this._defaultLoveText = defaultLoveText;
 	this._defaultHateText = defaultHateText;
 

Modified: dumbhippo/trunk/server/web/jsp-gnome/account.jsp
===================================================================
--- dumbhippo/trunk/server/web/jsp-gnome/account.jsp	2008-09-15 16:15:39 UTC (rev 7507)
+++ dumbhippo/trunk/server/web/jsp-gnome/account.jsp	2008-09-20 00:31:07 UTC (rev 7508)
@@ -20,6 +20,15 @@
 <jsp:setProperty name="account" property="facebookAuthToken" param="auth_token"/>
 <jsp:setProperty name="account" property="facebookErrorMessage" param="error_message"/>
 
+<dh:script modules="dh.account"/>
+<script type="text/javascript">
+    dh.account.dhNames = [ <c:forEach items="${account.gnomeSupportedAccounts.list}" var="supportedAccount" varStatus="status"><dh:jsString value="${supportedAccount.onlineAccountType.name}"/> ${status.last ? '];' : ','}</c:forEach>
+    dh.account.dhUserInfoTypes = [ <c:forEach items="${account.gnomeSupportedAccounts.list}" var="supportedAccount" varStatus="status"><dh:jsString value="${supportedAccount.userInfoType}"/> ${status.last ? '];' : ','}</c:forEach>
+    dh.account.dhValues = [ <c:forEach items="${account.gnomeSupportedAccounts.list}" var="supportedAccount" varStatus="status"><dh:jsString value="${supportedAccount.username}"/> ${status.last ? '];' : ','}</c:forEach>
+    dh.account.dhIds = [ <c:forEach items="${account.gnomeSupportedAccounts.list}" var="supportedAccount" varStatus="status"><dh:jsString value="${supportedAccount.id}"/> ${status.last ? '];' : ','}</c:forEach>
+    dh.account.dhDomIds = [ <c:forEach items="${account.gnomeSupportedAccounts.list}" var="supportedAccount" varStatus="status"><dh:jsString value="${supportedAccount.domNodeIdName}"/> ${status.last ? '];' : ','}</c:forEach>
+</script>
+
 <head>
     <gnome:title><c:out value="${person.viewedPerson.name}"/>'s Account</gnome:title>
 	<gnome:stylesheet name="site"/>

Modified: dumbhippo/trunk/server/web/jsp3/account.jsp
===================================================================
--- dumbhippo/trunk/server/web/jsp3/account.jsp	2008-09-15 16:15:39 UTC (rev 7507)
+++ dumbhippo/trunk/server/web/jsp3/account.jsp	2008-09-20 00:31:07 UTC (rev 7508)
@@ -25,6 +25,17 @@
 </c:if>
 
 <c:set var="pageName" value="Account" scope="page"/>
+
+<dh:script modules="dh.account"/>
+<script type="text/javascript">
+    dh.account.dhNames = [ <c:forEach items="${account.supportedAccounts.list}" var="supportedAccount" varStatus="status"><dh:jsString value="${supportedAccount.onlineAccountType.name}"/> ${status.last ? '];' : ','}</c:forEach>
+    dh.account.dhUserInfoTypes = [ <c:forEach items="${account.supportedAccounts.list}" var="supportedAccount" varStatus="status"><dh:jsString value="${supportedAccount.userInfoType}"/> ${status.last ? '];' : ','}</c:forEach>
+    dh.account.dhValues = [ <c:forEach items="${account.supportedAccounts.list}" var="supportedAccount" varStatus="status"><dh:jsString value="${supportedAccount.username}"/> ${status.last ? '];' : ','}</c:forEach>
+    dh.account.dhHateQuips = [ <c:forEach items="${account.supportedAccounts.list}" var="supportedAccount" varStatus="status"><dh:jsString value="${supportedAccount.hateQuip}"/> ${status.last ? '];' : ','}</c:forEach>
+    dh.account.dhIds = [ <c:forEach items="${account.supportedAccounts.list}" var="supportedAccount" varStatus="status"><dh:jsString value="${supportedAccount.id}"/> ${status.last ? '];' : ','}</c:forEach>
+    dh.account.dhDomIds = [ <c:forEach items="${account.supportedAccounts.list}" var="supportedAccount" varStatus="status"><dh:jsString value="${supportedAccount.domNodeIdName}"/> ${status.last ? '];' : ','}</c:forEach>
+    dh.account.dhMugshotEnabledFlags = [ <c:forEach items="${account.supportedAccounts.list}" var="supportedAccount" varStatus="status"><dh:jsString value="${supportedAccount.mugshotEnabled}"/> ${status.last ? '];' : ','}</c:forEach>
+</script>
     
 <head>
     <title><c:out value="${person.viewedPerson.name}"/>'s ${pageName} - Mugshot</title>

Modified: dumbhippo/trunk/server/web/tags/3/accountEditTableExternals.tag
===================================================================
--- dumbhippo/trunk/server/web/tags/3/accountEditTableExternals.tag	2008-09-15 16:15:39 UTC (rev 7507)
+++ dumbhippo/trunk/server/web/tags/3/accountEditTableExternals.tag	2008-09-20 00:31:07 UTC (rev 7508)
@@ -130,19 +130,16 @@
 	    </dht2:formTableRow>
 	    <c:if test="${dh:enumIs(site, 'GNOME')}">
 	        <c:forEach items="${account.gnomeSupportedAccounts.list}" var="supportedAccount">
-	            <c:if test="${supportedAccount.siteName == 'Twitter'}">
-	                <dht2:formTableRow controlId="dh${supportedAccount.domNodeIdName}"
-				                       label="${supportedAccount.siteName}"				
-				                       icon="/images3/${buildStamp}/${supportedAccount.iconName}">
-	                    <gnome:loveEntry name="${supportedAccount.siteName}"
-				  	   	  	             userInfoType="${supportedAccount.siteUserInfoType}"
-							             isInfoTypeProvidedBySite="${supportedAccount.infoTypeProvidedBySite}"
-							             link="${supportedAccount.externalAccountType.siteLink}"
-							             baseId="dh${supportedAccount.domNodeIdName}"
-							             mode="${supportedAccount.sentiment}">				        
-			            </gnome:loveEntry>
-			        </dht2:formTableRow>			
-			    </c:if>
+	            <dht2:formTableRow controlId="dh${supportedAccount.domNodeIdName}"
+		                           label="${supportedAccount.siteName}"				
+			                       icon="/images3/${buildStamp}/${supportedAccount.iconName}">
+                    <gnome:loveEntry name="${supportedAccount.siteName}"
+			  	   	  	             userInfoType="${supportedAccount.userInfoType}"
+							         link="${supportedAccount.onlineAccountType.site}"
+					                 baseId="dh${supportedAccount.domNodeIdName}"
+					                 mode="${supportedAccount.sentiment}">				        
+			        </gnome:loveEntry>
+			    </dht2:formTableRow>			
 	        </c:forEach>
 	    </c:if>
 	</c:if>    
@@ -210,13 +207,15 @@
 						</c:choose>
 					</c:when>
 					<c:otherwise>
+					    <%-- it's ok to assume that supportedAccount.externalAccountType is not null here --%>
+					    <%-- because the list of supported accounts for Mugshot is generated based on the ExternalAccountType enumeration --%> 
 						<dht2:loveHateEntry name="${supportedAccount.siteName}"
-							userInfoType="${supportedAccount.siteUserInfoType}"
-							isInfoTypeProvidedBySite="${supportedAccount.infoTypeProvidedBySite}"
+							userInfoType="${supportedAccount.externalAccountType.siteUserInfoType}"
+							isInfoTypeProvidedBySite="${supportedAccount.externalAccountType.infoTypeProvidedBySite}"
 							link="${supportedAccount.externalAccountType.siteLink}"
 							baseId="dh${supportedAccount.domNodeIdName}"
 							mode="${supportedAccount.sentiment}">
-
+					                 
 							<c:if test="${supportedAccount.siteName == 'Amazon'}">
 								<div class="dh-amazon-details">
 								<c:forEach items="${account.amazonLinks}" var="amazonLinkPair">

Modified: dumbhippo/trunk/server/web/tags/3/accountJavascriptSetup.tag
===================================================================
--- dumbhippo/trunk/server/web/tags/3/accountJavascriptSetup.tag	2008-09-15 16:15:39 UTC (rev 7507)
+++ dumbhippo/trunk/server/web/tags/3/accountJavascriptSetup.tag	2008-09-20 00:31:07 UTC (rev 7508)
@@ -18,32 +18,4 @@
 	dh.account.reloadPhoto = function() {
 		dh.photochooser.reloadPhoto([document.getElementById('dhHeadshotImageContainer')], 60);
 	}
-	dh.account.initialMyspaceName = <dh:jsString value="${account.mySpaceName}"/>;
-	dh.account.initialMyspaceHateQuip = <dh:jsString value="${account.mySpaceHateQuip}"/>;
-	dh.account.initialYouTubeName = <dh:jsString value="${account.youTubeName}"/>;
-	dh.account.initialYouTubeHateQuip = <dh:jsString value="${account.youTubeHateQuip}"/>;
-	dh.account.initialLastFmName = <dh:jsString value="${account.lastFmName}"/>;
-	dh.account.initialLastFmHateQuip = <dh:jsString value="${account.lastFmHateQuip}"/>;			
-	dh.account.initialFlickrEmail = <dh:jsString value="${account.flickrEmail}"/>;
-	dh.account.initialFlickrHateQuip = <dh:jsString value="${account.flickrHateQuip}"/>;
-	dh.account.initialLinkedInName = <dh:jsString value="${account.linkedInName}"/>;
-	dh.account.initialLinkedInHateQuip = <dh:jsString value="${account.linkedInHateQuip}"/>;
-	dh.account.initialRhapsodyUrl = <dh:jsString value="${account.rhapsodyListeningHistoryFeedUrl}"/>;
-	dh.account.initialRhapsodyHateQuip = <dh:jsString value="${account.rhapsodyHateQuip}"/>;	
-	dh.account.initialDeliciousName = <dh:jsString value="${account.deliciousName}"/>;
-	dh.account.initialDeliciousHateQuip = <dh:jsString value="${account.deliciousHateQuip}"/>;	
-	dh.account.initialTwitterName = <dh:jsString value="${account.twitterName}"/>;
-	dh.account.initialTwitterHateQuip = <dh:jsString value="${account.twitterHateQuip}"/>;
-	dh.account.initialDiggName = <dh:jsString value="${account.diggName}"/>;
-	dh.account.initialDiggHateQuip = <dh:jsString value="${account.diggHateQuip}"/>;
-	dh.account.initialRedditName = <dh:jsString value="${account.redditName}"/>;
-	dh.account.initialRedditHateQuip = <dh:jsString value="${account.redditHateQuip}"/>;					
-	dh.account.initialNetflixUrl = <dh:jsString value="${account.netflixFeedUrl}"/>;
-	dh.account.initialNetflixHateQuip = <dh:jsString value="${account.netflixHateQuip}"/>;	
-	dh.account.initialGoogleReaderUrl = <dh:jsString value="${account.googleReaderUrl}"/>;
-	dh.account.initialGoogleReaderHateQuip = <dh:jsString value="${account.googleReaderHateQuip}"/>;
-	dh.account.initialPicasaName = <dh:jsString value="${account.picasaName}"/>;
-	dh.account.initialPicasaHateQuip = <dh:jsString value="${account.picasaHateQuip}"/>;
-	dh.account.initialAmazonUrl = <dh:jsString value="${account.amazonUrl}"/>;
-	dh.account.initialAmazonHateQuip = <dh:jsString value="${account.amazonHateQuip}"/>;
 </script>

Modified: dumbhippo/trunk/server/web/tags/gnome/loveEntry.tag
===================================================================
--- dumbhippo/trunk/server/web/tags/gnome/loveEntry.tag	2008-09-15 16:15:39 UTC (rev 7507)
+++ dumbhippo/trunk/server/web/tags/gnome/loveEntry.tag	2008-09-20 00:31:07 UTC (rev 7508)
@@ -4,7 +4,6 @@
 
 <%@ attribute name="name" required="true" type="java.lang.String" %>
 <%@ attribute name="userInfoType" required="true" type="java.lang.String" %>
-<%@ attribute name="isInfoTypeProvidedBySite" required="true" type="java.lang.Boolean" %>
 <%@ attribute name="link" required="true" type="java.lang.String" %>
 <%@ attribute name="baseId" required="true" type="java.lang.String" %>
 <%@ attribute name="mode" required="true" type="java.lang.String" %>



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]