4d2eb49f84199890ac7e679ba67f094cb49bd124
braney
  Thu Apr 6 14:29:02 2023 -0700
Change recommended track sets to merge with the current session.  refs #28525

diff --git src/hg/lib/cart.c src/hg/lib/cart.c
index a3d4185..c845934 100644
--- src/hg/lib/cart.c
+++ src/hg/lib/cart.c
@@ -155,60 +155,80 @@
 if (!sqlTableExists(conn, userDbTable()))
     {
     fprintf(stderr, "cartTablesOk failed on %s.%s  pid=%ld\n",
 	    sqlGetDatabase(conn), userDbTable(),  (long)getpid());
     return FALSE;
     }
 if (!sqlTableExists(conn, sessionDbTable()))
     {
     fprintf(stderr, "cartTablesOk failed on %s.%s  pid=%ld\n",
 	    sqlGetDatabase(conn), sessionDbTable(), (long)getpid());
     return FALSE;
     }
 return TRUE;
 }
 
-void cartParseOverHashExt(struct cart *cart, char *contents, boolean merge)
-/* Parse cgi-style contents into a hash table.  If merge is FALSE, this will *not*
- * replace existing members of hash that have same name, so we can
- * support multi-select form inputs (same var name can have multiple
- * values which will be in separate hashEl's). If merge is TRUE, we
- * replace existing values with new values */
+static void mergeHash(struct hash *first, struct hash *second)
+/* Merge one hash on top of another. */
+{
+struct hashCookie cookie = hashFirst(second);
+struct hashEl *hel;
+while ((hel = hashNext(&cookie)) != NULL)
+    hashReplace(first, hel->name, hel->val);
+}
+
+static void loadHash(struct hash *hash, char *contents)
+/* Load a hash from a cart-like string. */
 {
-struct hash *hash = cart->hash;
 char *namePt, *dataPt, *nextNamePt;
 namePt = contents;
 while (namePt != NULL && namePt[0] != 0)
     {
     dataPt = strchr(namePt, '=');
     if (dataPt == NULL)
 	errAbort("Mangled input string %s", namePt);
     *dataPt++ = 0;
     nextNamePt = strchr(dataPt, '&');
     if (nextNamePt == NULL)
 	nextNamePt = strchr(dataPt, ';');	/* Accomodate DAS. */
     if (nextNamePt != NULL)
          *nextNamePt++ = 0;
     cgiDecode(dataPt,dataPt,strlen(dataPt));
-    if (!merge)
     hashAdd(hash, namePt, cloneString(dataPt));
-    else 
-        hashReplace(hash, namePt, cloneString(dataPt));
     namePt = nextNamePt;
     }
 }
 
+void cartParseOverHashExt(struct cart *cart, char *contents, boolean merge)
+/* Parse cgi-style contents into a hash table.  If merge is FALSE, this will *not*
+ * replace existing members of hash that have same name, so we can
+ * support multi-select form inputs (same var name can have multiple
+ * values which will be in separate hashEl's). If merge is TRUE, we
+ * replace existing values with new values */
+{
+if (merge)
+    {
+    struct hash *newHash = newHash(8);
+
+    loadHash(newHash, contents);
+    mergeHash(newHash, cart->hash);
+    cart->hash = newHash;
+    }
+else
+    loadHash(cart->hash, contents);
+}
+
 void cartParseOverHash(struct cart *cart, char *contents)
 /* Parse cgi-style contents into a hash table.  This will *not*
  * replace existing members of hash that have same name, so we can
  * support multi-select form inputs (same var name can have multiple
  * values which will be in separate hashEl's). */
 {
 cartParseOverHashExt(cart, contents, FALSE);
 }
 
 static boolean looksCorrupted(struct cartDb *cdb)
 /* Test for db corruption by checking format of firstUse field. */
 {
 if (cdb == NULL)
     return FALSE;
 else
@@ -322,30 +342,41 @@
 		 "MySQL problem??", id);
     if (!sameString(sessionKey,""))
 	freeMem(sessionKey);
     }
 *found = result;
 return cdb;
 }
 
 void cartExclude(struct cart *cart, char *var)
 /* Exclude var from persistent storage. */
 {
 hashAdd(cart->exclude, var, NULL);
 }
 
 
+static char *_cartNamedSessionDbTable = NULL;
+
+char *cartNamedSessionDbTable()
+/* Get the name of the table that lists named sessions.  Don't free the result. */
+{
+if (_cartNamedSessionDbTable == NULL)
+    _cartNamedSessionDbTable = cfgOptionEnvDefault("HGDB_NAMED_SESSION_DB", namedSessionDbTableConfVariable,
+                                             defaultNamedSessionDb);
+return _cartNamedSessionDbTable;
+}
+
 void sessionTouchLastUse(struct sqlConnection *conn, char *encUserName,
 			 char *encSessionName)
 /* Increment namedSessionDb.useCount and update lastUse for this session. */
 {
 struct dyString *dy = dyStringNew(1024);
 int useCount;
 sqlDyStringPrintf(dy, "SELECT useCount FROM %s "
 	       "WHERE userName = '%s' AND sessionName = '%s';",
 	       namedSessionTable, encUserName, encSessionName);
 useCount = sqlQuickNum(conn, dy->string) + 1;
 dyStringClear(dy);
 sqlDyStringPrintf(dy, "UPDATE %s SET useCount = %d, lastUse=now() "
 	       "WHERE userName = '%s' AND sessionName = '%s';",
 	       namedSessionTable, useCount, encUserName, encSessionName);
 sqlUpdate(conn, dy->string);