533112afe2a2005e80cdb1f82904ea65032d4302
braney
  Sat Oct 2 11:37:34 2021 -0700
split hg/lib into two separate libaries, one only used by the cgis

diff --git src/hg/cgilib/cartJson.c src/hg/cgilib/cartJson.c
new file mode 100644
index 0000000..2d504ff
--- /dev/null
+++ src/hg/cgilib/cartJson.c
@@ -0,0 +1,898 @@
+/* cartJson - parse and execute JSON commands to update cart and/or return cart data as JSON. */
+#include "common.h"
+#include "cartJson.h"
+#include "cartTrackDb.h"
+#include "cheapcgi.h"
+#include "errCatch.h"
+#include "grp.h"
+#include "hdb.h"
+#include "hgFind.h"
+#include "htmshell.h"
+#include "hubConnect.h"
+#include "hui.h"
+#include "jsonParse.h"
+#include "obscure.h"
+#include "regexHelper.h"
+#include "suggest.h"
+#include "trackDb.h"
+#include "trackHub.h"
+#include "web.h"
+
+char *cartJsonOptionalParam(struct hash *paramHash, char *name)
+/* Convenience function for a CartJsonHandler function: Look up name in paramHash.
+ * Return the string contained in its jsonElement value, or NULL if not found. */
+{
+struct jsonElement *jel = hashFindVal(paramHash, name);
+if (jel)
+    return jsonStringVal(jel, name);
+return NULL;
+}
+
+char *cartJsonParamDefault(struct hash *paramHash, char *name, char *defaultVal)
+/* Convenience function for a CartJsonHandler function: Look up name in paramHash.
+ * Return the string contained in its jsonElement value, or defaultVal if not found. */
+{
+struct jsonElement *jel = hashFindVal(paramHash, name);
+if (jel)
+    return jsonStringVal(jel, name);
+return defaultVal;
+}
+
+char *cartJsonRequiredParam(struct hash *paramHash, char *name, struct jsonWrite *jw, char *context)
+/* Convenience function for a CartJsonHandler function: Look up name in paramHash.
+ * If found, return the string contained in its jsonElement value.
+ * If not, write an error message (using context) and return NULL. */
+{
+char *param = cartJsonOptionalParam(paramHash, name);
+if (param == NULL)
+    jsonWriteStringf(jw, "error",
+		     "%s: required param %s is missing", context, name);
+return param;
+}
+
+static char *stripAnchor(char *textIn)
+/* If textIn contains an HTML anchor tag, strip it out (and its end tag). */
+{
+regmatch_t matches[3];
+if (regexMatchSubstrNoCase(textIn, "<a href[^>]+>", matches, ArraySize(matches)))
+    {
+    char *textOut = cloneString(textIn);
+    memmove(textOut+matches[0].rm_so, textOut+matches[0].rm_eo,
+	    strlen(textOut) + 1 - matches[0].rm_eo);
+    if (regexMatchSubstrNoCase(textOut, "</a>", matches, ArraySize(matches)))
+	memmove(textOut+matches[0].rm_so, textOut+matches[0].rm_eo,
+		strlen(textOut) + 1 - matches[0].rm_eo);
+    return textOut;
+    }
+return textIn;
+}
+
+static void hgPositionsJson(struct jsonWrite *jw, char *db, struct hgPositions *hgp, struct cart *cart)
+/* Write out JSON description of multiple position matches. */
+{
+struct hgPosTable *table;
+jsonWriteListStart(jw, "positionMatches");
+struct trackDb *tdbList = NULL;
+for (table = hgp->tableList; table != NULL; table = table->next)
+    {
+    if (table->posList != NULL)
+	{
+	char *tableName = table->name;
+	// clear the tdb cache if this track is a hub track
+	if (isHubTrack(tableName))
+	    tdbList = NULL;
+	struct trackDb *tdb = tdbForTrack(db, tableName, &tdbList);
+	if (!tdb && startsWith("all_", tableName))
+            tdb = tdbForTrack(db, tableName+strlen("all_"), &tdbList);
+        if (!tdb)
+            errAbort("no track for table \"%s\" found via a findSpec", tableName);
+	char *trackName = tdb->track;
+	jsonWriteObjectStart(jw, NULL);
+	jsonWriteString(jw, "name", table->name);
+	jsonWriteString(jw, "trackName", trackName);
+	jsonWriteString(jw, "description", table->description);
+	jsonWriteString(jw, "vis", hCarefulTrackOpenVis(db, trackName));
+	jsonWriteListStart(jw, "matches");
+	struct hgPos *pos;
+	for (pos = table->posList; pos != NULL; pos = pos->next)
+	    {
+	    char *encMatches = cgiEncode(pos->browserName);
+	    jsonWriteObjectStart(jw, NULL); // begin one match
+	    if (pos->chrom != NULL)
+		jsonWriteStringf(jw, "position", "%s:%d-%d",
+				 pos->chrom, pos->chromStart+1, pos->chromEnd);
+	    else
+		// GenBank results set position to GB accession instead of chr:s-e position.
+		jsonWriteString(jw, "position", pos->name);
+	    // this is magic to tell the browser to make the
+	    // composite and this subTrack visible
+	    if (tdb->parent)
+		{
+		if (tdbIsSuperTrackChild(tdb))
+		    jsonWriteStringf(jw, "extraSel", "%s=show&", tdb->parent->track);
+		else
+		    {
+		    // tdb is a subtrack of a composite or a view
+		    jsonWriteStringf(jw, "extraSel", "%s_sel=1&%s_sel=1&",
+				     trackName, tdb->parent->track);
+		    }
+		}
+	    jsonWriteString(jw, "hgFindMatches", encMatches);
+	    jsonWriteString(jw, "posName", htmlEncode(pos->name));
+	    if (pos->description)
+		{
+		stripString(pos->description, "\n");
+		jsonWriteString(jw, "description", stripAnchor(pos->description));
+		}
+	    jsonWriteObjectEnd(jw); // end one match
+	    }
+	jsonWriteListEnd(jw); // end matches
+	jsonWriteObjectEnd(jw); // end one table
+	}
+    }
+    jsonWriteListEnd(jw); // end positionMatches
+}
+
+static struct hgPositions *genomePosCJ(struct jsonWrite *jw,
+				       char *db, char *spec, char **retChromName,
+				       int *retWinStart, int *retWinEnd, struct cart *cart)
+/* Search for positions in genome that match user query.
+ * Return an hgp unless there is a problem.  hgp->singlePos will be set if a single
+ * position matched.
+ * Otherwise display list of positions, put # of positions in retWinStart,
+ * and return NULL. */
+{
+char *hgAppName = "cartJson";
+struct hgPositions *hgp = NULL;
+char *chrom = NULL;
+int start = BIGNUM;
+int end = 0;
+
+char *terms[16];
+int termCount = chopByChar(cloneString(spec), ';', terms, ArraySize(terms));
+boolean multiTerm = (termCount > 1);
+
+int i = 0;
+for (i = 0;  i < termCount;  i++)
+    {
+    trimSpaces(terms[i]);
+    if (isEmpty(terms[i]))
+	continue;
+    hgp = hgPositionsFind(db, terms[i], "", hgAppName, cart, multiTerm);
+    if (hgp == NULL || hgp->posCount == 0)
+	{
+	jsonWriteStringf(jw, "error",
+			 "Sorry, couldn't locate %s in %s %s", htmlEncode(terms[i]),
+                         trackHubSkipHubName(hOrganism(db)), hFreezeDate(db));
+	if (multiTerm)
+	    jsonWriteStringf(jw, "error",
+			     "%s not uniquely determined -- can't do multi-position search.",
+			     terms[i]);
+	*retWinStart = 0;
+	return NULL;
+	}
+    if (hgp->singlePos != NULL)
+	{
+	if (chrom != NULL && !sameString(chrom, hgp->singlePos->chrom))
+	    {
+	    jsonWriteStringf(jw, "error",
+			     "Sites occur on different chromosomes: %s, %s.",
+			     chrom, hgp->singlePos->chrom);
+	    return NULL;
+	    }
+	chrom = hgp->singlePos->chrom;
+	if (hgp->singlePos->chromStart < start)
+	    start = hgp->singlePos->chromStart;
+	if (hgp->singlePos->chromEnd > end)
+	    end = hgp->singlePos->chromEnd;
+	}
+    else
+	{
+	hgPositionsJson(jw, db, hgp, cart);
+	if (multiTerm && hgp->posCount != 1)
+	    {
+	    jsonWriteStringf(jw, "error",
+			     "%s not uniquely determined (%d locations) -- "
+			     "can't do multi-position search.",
+			     terms[i], hgp->posCount);
+	    return NULL;
+	    }
+	break;
+	}
+    }
+if (hgp != NULL)
+    {
+    *retChromName = chrom;
+    *retWinStart  = start;
+    *retWinEnd    = end;
+    }
+return hgp;
+}
+
+static void changePosition(struct cartJson *cj, char *newPosition)
+/* Update position in cart, after performing lookup if necessary.
+ * Usually we don't report what we just changed, but since we might modify it,
+ * print out the final value. */
+{
+char *db = cartString(cj->cart, "db");
+char *chrom = NULL;
+int start=0, end=0;
+struct hgPositions *hgp = genomePosCJ(cj->jw, db, newPosition, &chrom, &start, &end, cj->cart);
+// If it resolved to a single position, update the cart; otherwise the app can
+// present the error (or list of matches) to the user.
+if (hgp && hgp->singlePos)
+    {
+    char newPosBuf[128];
+    safef(newPosBuf, sizeof(newPosBuf), "%s:%d-%d", chrom, start+1, end);
+    cartSetString(cj->cart, "position", newPosBuf);
+    jsonWriteString(cj->jw, "position", newPosBuf);
+    }
+else
+    // Search failed; restore position from cart
+    jsonWriteString(cj->jw, "position", cartUsualString(cj->cart, "position", hDefaultPos(db)));
+}
+
+static void changePositionHandler(struct cartJson *cj, struct hash *paramHash)
+/* Update position in cart, after performing lookup if necessary.
+ * Usually we don't report what we just changed, but since we might modify it,
+ * print out the final value. */
+{
+char *newPosition = cartJsonRequiredParam(paramHash, "newValue", cj->jw, "changePosition");
+if (newPosition)
+    changePosition(cj, newPosition);
+}
+
+static void printGeneSuggestTrack(struct cartJson *cj, char *db)
+/* Get the gene track used by hgSuggest for db (defaulting to cart db), or null if
+ * there is none for this assembly. */
+{
+if (isEmpty(db))
+    db = cartString(cj->cart, "db");
+char *track = NULL;
+if (! trackHubDatabase(db))
+    track = assemblyGeneSuggestTrack(db);
+jsonWriteString(cj->jw, "geneSuggestTrack", track);
+}
+
+static void getGeneSuggestTrack(struct cartJson *cj, struct hash *paramHash)
+/* Get the gene track used by hgSuggest for db (defaulting to cart db), or null if
+ * there is none for this assembly. */
+{
+char *db = cartJsonOptionalParam(paramHash, "db");
+printGeneSuggestTrack(cj, db);
+}
+
+static void getVar(struct cartJson *cj, struct hash *paramHash)
+/* Print out the requested cart var(s). varString may be a comma-separated list.
+ * If a var is a list variable, prints out a list of values for that var. */
+{
+char *varString = cartJsonRequiredParam(paramHash, "var", cj->jw, "get");
+if (! varString)
+    return;
+struct slName *varList = slNameListFromComma(varString), *var;
+for (var = varList;  var != NULL;  var = var->next)
+    {
+    if (cartListVarExists(cj->cart, var->name))
+	{
+	// Use cartOptionalSlNameList and return a list:
+	struct slName *valList = cartOptionalSlNameList(cj->cart, var->name);
+	jsonWriteSlNameList(cj->jw, var->name, valList);
+	slFreeList(&valList);
+	}
+    else
+	{
+	// Regular single-value variable (or not in the cart):
+	char *val = cartOptionalString(cj->cart, var->name);
+	jsonWriteString(cj->jw, var->name, val);
+	}
+    }
+slFreeList(&varList);
+}
+
+INLINE boolean nameIsTdbField(char *name)
+/* Return TRUE if name is a tdb->{field}, e.g. "track" or "shortLabel" etc. */
+{
+static char *tdbFieldNames[] =
+    { "track", "table", "shortLabel", "longLabel", "type", "priority", "grp", "parent",
+      "subtracks", "visibility" };
+return (stringArrayIx(name, tdbFieldNames, ArraySize(tdbFieldNames)) >= 0);
+}
+
+INLINE boolean fieldOk(char *field, struct hash *fieldHash)
+/* Return TRUE if fieldHash is NULL or field exists in fieldHash. */
+{
+return (fieldHash == NULL || hashLookup(fieldHash, field));
+}
+
+static void writeTdbSimple(struct jsonWrite *jw, struct trackDb *tdb, struct hash *fieldHash)
+/* Write JSON for the non-parent/child fields of tdb */
+{
+if (fieldOk("track", fieldHash))
+    jsonWriteString(jw, "track", tdb->track);
+if (fieldOk("table", fieldHash))
+    jsonWriteString(jw, "table", tdb->table);
+if (fieldOk("shortLabel", fieldHash))
+    jsonWriteString(jw, "shortLabel", tdb->shortLabel);
+if (fieldOk("longLabel", fieldHash))
+    jsonWriteString(jw, "longLabel", tdb->longLabel);
+if (fieldOk("type", fieldHash))
+    jsonWriteString(jw, "type", tdb->type);
+if (fieldOk("priority", fieldHash))
+    jsonWriteDouble(jw, "priority", tdb->priority);
+if (fieldOk("grp", fieldHash))
+    jsonWriteString(jw, "grp", tdb->grp);
+// NOTE: if you add a new field here, then also add it to nameIsTdbField above.
+if (tdb->settingsHash)
+    {
+    struct hashEl *hel;
+    struct hashCookie cookie = hashFirst(tdb->settingsHash);
+    while ((hel = hashNext(&cookie)) != NULL)
+        {
+        if (! nameIsTdbField(hel->name) && fieldOk(hel->name, fieldHash))
+            jsonWriteString(jw, hel->name, (char *)hel->val);
+        }
+    if (fieldOk("noGenome", fieldHash))
+        {
+        if ((hel = hashLookup(tdb->settingsHash, "tableBrowser")) != NULL)
+            {
+            if (startsWithWord("noGenome", (char *)(hel->val)))
+                jsonWriteBoolean(jw, "noGenome", TRUE);
+            }
+        }
+    }
+}
+
+static int trackDbViewCmp(const void *va, const void *vb)
+/* *** I couldn't find anything like this in hgTracks or hui, but clearly views end up
+ * *** being ordered alphabetically somehow.  Anyway...
+ * Compare two trackDbs, first by view if both have a view setting, then the usual way
+ * (by priority, then by shortLabel). */
+{
+const struct trackDb *a = *((struct trackDb **)va);
+const struct trackDb *b = *((struct trackDb **)vb);
+// The casts are necessary here unless one adds const to trackDbSetting decl
+char *viewA = trackDbSetting((struct trackDb *)a, "view");
+char *viewB = trackDbSetting((struct trackDb *)b, "view");
+int diff = 0;
+if (isNotEmpty(viewA) && isNotEmpty(viewB))
+    diff = strcmp(viewA, viewB);
+if (diff != 0)
+    return diff;
+return trackDbCmp(va, vb);
+}
+
+static struct jsonWrite *rTdbToJw(struct trackDb *tdb, struct hash *fieldHash,
+                                  struct hash *excludeTypesHash, int depth, int maxDepth)
+/* Recursively build and return a new jsonWrite object with JSON for tdb and its children,
+ * or NULL if tdb or all children have been filtered out by excludeTypesHash.
+ * If excludeTypesHash is non-NULL, omit any tracks/views/subtracks with type in excludeTypesHash.
+ * If fieldHash is non-NULL, include only the field names indexed in fieldHash. */
+{
+if (maxDepth >= 0 && depth > maxDepth)
+    return NULL;
+boolean doSubtracks = (tdb->subtracks && fieldOk("subtracks", fieldHash));
+// If excludeTypesHash is given and tdb is a leaf track/subtrack, look up the first word
+// of tdb->type in excludeTypesHash; if found, return NULL.
+if (excludeTypesHash && !doSubtracks)
+    {
+    char typeCopy[PATH_LEN];
+    safecpy(typeCopy, sizeof(typeCopy), tdb->type);
+    if (hashLookup(excludeTypesHash, firstWordInLine(typeCopy)))
+        return NULL;
+    }
+boolean gotSomething = !doSubtracks;
+struct jsonWrite *jwNew = jsonWriteNew();
+jsonWriteObjectStart(jwNew, NULL);
+writeTdbSimple(jwNew, tdb, fieldHash);
+if (tdb->parent && fieldOk("parent", fieldHash))
+    {
+    // We can't link to an object in JSON and better not recurse here or else infinite loop.
+    if (tdbIsSuperTrackChild(tdb))
+        {
+        // Supertracks have been omitted from fullTrackList, so add the supertrack object's
+        // non-parent/child info here.
+        jsonWriteObjectStart(jwNew, "parent");
+        writeTdbSimple(jwNew, tdb->parent, fieldHash);
+        jsonWriteObjectEnd(jwNew);
+        }
+    else
+        // Just the name so we don't have infinite loops.
+        jsonWriteString(jwNew, "parent", tdb->parent->track);
+    }
+if (doSubtracks)
+    {
+    jsonWriteListStart(jwNew, "subtracks");
+    slSort(&tdb->subtracks, trackDbViewCmp);
+    struct trackDb *subTdb;
+    for (subTdb = tdb->subtracks;  subTdb != NULL;  subTdb = subTdb->next)
+        {
+        struct jsonWrite *jwSub = rTdbToJw(subTdb, fieldHash, excludeTypesHash, depth+1, maxDepth);
+        if (jwSub)
+            {
+            gotSomething = TRUE;
+            jsonWriteAppend(jwNew, NULL, jwSub);
+            jsonWriteFree(&jwSub);
+            }
+        }
+    jsonWriteListEnd(jwNew);
+    }
+jsonWriteObjectEnd(jwNew);
+if (! gotSomething)
+    // All children were excluded; clean up and null out jwNew.
+    jsonWriteFree(&jwNew);
+return jwNew;
+}
+
+static struct hash *hashFromCommaString(char *commaString)
+/* Return a hash that stores words in comma-separated string, or NULL if string is NULL or empty. */
+{
+struct hash *hash = NULL;
+if (isNotEmpty(commaString))
+    {
+    hash = hashNew(0);
+    char *words[1024];
+    int i, wordCount = chopCommas(commaString, words);
+    for (i = 0;  i < wordCount;  i++)
+        hashStoreName(hash, words[i]);
+    }
+return hash;
+}
+
+static struct hash *hashTracksByGroup(struct trackDb *trackList)
+/* Hash group names to lists of tracks in those groups; sort each list by trackDb priority. */
+{
+struct hash *hash = hashNew(0);
+struct trackDb *tdb;
+for (tdb = trackList;  tdb != NULL;  tdb = tdb->next)
+    {
+    struct hashEl *hel = hashLookup(hash, tdb->grp);
+    struct slRef *slr = slRefNew(tdb);
+    if (hel)
+	slAddHead(&(hel->val), slr);
+    else
+	hashAdd(hash, tdb->grp, slr);
+    }
+struct hashCookie cookie = hashFirst(hash);
+struct hashEl *hel;
+while ((hel = hashNext(&cookie)) != NULL)
+    slSort(&hel->val, trackDbRefCmp);
+return hash;
+}
+
+int trackDbRefCmpShortLabel(const void *va, const void *vb)
+/* Do trackDbCmpShortLabel on list of references as opposed to actual trackDbs. */
+{
+const struct slRef *aRef = *((struct slRef **)va);
+const struct slRef *bRef = *((struct slRef **)vb);
+struct trackDb *a = aRef->val, *b = bRef->val;
+return trackDbCmpShortLabel(&a, &b);
+}
+
+static struct slRef *sortedAllTracks(struct trackDb *trackList)
+/* Return an slRef list containing all tdbs in track list, sorted by shortLabel. */
+{
+struct slRef *trackRefList = NULL;
+struct trackDb *tdb;
+for (tdb = trackList;  tdb != NULL;  tdb = tdb->next)
+    slAddHead(&trackRefList, slRefNew(tdb));
+slSort(&trackRefList, trackDbRefCmpShortLabel);
+return trackRefList;
+}
+
+static boolean writeGroupedTrack(struct jsonWrite *jw, char *name, char *label,
+                                 struct hash *fieldHash, struct hash *excludeTypesHash,
+                                 int maxDepth, struct slRef *tdbRefList)
+/* If tdbRefList is empty after excluding tracks/views/subtracks whose types are
+ * in excludeTypesHash, then return FALSE and write nothing.  Otherwise write a group
+ * and its tracks/views/subtracks and return TRUE. */
+{
+// Make a new jsonWrite object in case this group turns out to have no children after filtering.
+struct jsonWrite *jwNew = jsonWriteNew();
+jsonWriteObjectStart(jwNew, NULL);
+jsonWriteString(jwNew, "name", name);
+jsonWriteString(jwNew, "label", label);
+jsonWriteListStart(jwNew, "tracks");
+boolean gotSomething = FALSE;
+struct slRef *tdbRef;
+for (tdbRef = tdbRefList;  tdbRef != NULL;  tdbRef = tdbRef->next)
+    {
+    struct trackDb *tdb = tdbRef->val;
+    // First see if there are any tracks to show for this group:
+    struct jsonWrite *jwTrack = rTdbToJw(tdb, fieldHash, excludeTypesHash, 1, maxDepth);
+    if (jwTrack)
+        {
+        gotSomething = TRUE;
+        jsonWriteAppend(jwNew, NULL, jwTrack);
+        jsonWriteFree(&jwTrack);
+        }
+    }
+if (gotSomething)
+    {
+    // Group has at least one track, so append it to jw.
+    jsonWriteListEnd(jwNew);
+    jsonWriteObjectEnd(jwNew);
+    jsonWriteAppend(jw, NULL, jwNew);
+    }
+jsonWriteFree(&jwNew);
+return gotSomething;
+}
+
+void cartJsonGetGroupedTrackDb(struct cartJson *cj, struct hash *paramHash)
+/* Translate trackDb list (only a subset of the fields) into JSON array of track group objects;
+ * each group contains an array of track objects that may have subtracks.  Send it in a wrapper
+ * object that includes the database from which it was taken; it's possible that by the time
+ * this reaches the client, the user might have switched to a new db. */
+{
+struct jsonWrite *jw = cj->jw;
+struct trackDb *fullTrackList = NULL;
+struct grp *fullGroupList = NULL;
+struct errCatch *errCatch = errCatchNew();
+if (errCatchStart(errCatch))
+    {
+    cartTrackDbInit(cj->cart, &fullTrackList, &fullGroupList, /* useAccessControl=*/TRUE);
+    }
+errCatchEnd(errCatch);
+if (errCatch->gotError)
+    {
+    warn("%s", errCatch->message->string);
+    jsonWriteObjectStart(jw, "groupedTrackDb");
+    jsonWriteString(jw, "db", cartString(cj->cart, "db"));
+    jsonWriteListStart(jw, "groupedTrackDb");
+    jsonWriteListEnd(jw);
+    jsonWriteObjectEnd(jw);
+    return;
+    }
+errCatchFree(&errCatch);
+struct hash *groupedTrackRefList = hashTracksByGroup(fullTrackList);
+// If the optional param 'fields' is given, hash the field names that should be returned.
+char *fields = cartJsonOptionalParam(paramHash, "fields");
+struct hash *fieldHash = hashFromCommaString(fields);
+char *excludeTypes = cartJsonOptionalParam(paramHash, "excludeTypes");
+struct hash *excludeTypesHash = hashFromCommaString(excludeTypes);
+// Also check for optional parameter 'maxDepth':
+int maxDepth = -1;
+char *maxDepthStr = cartJsonOptionalParam(paramHash, "maxDepth");
+if (isNotEmpty(maxDepthStr))
+    maxDepth = atoi(maxDepthStr);
+jsonWriteObjectStart(jw, "groupedTrackDb");
+jsonWriteString(jw, "db", cartString(cj->cart, "db"));
+jsonWriteListStart(jw, "groupedTrackDb");
+int nonEmptyGroupCount = 0;
+struct grp *grp;
+for (grp = fullGroupList;  grp != NULL;  grp = grp->next)
+    {
+    struct slRef *tdbRefList = hashFindVal(groupedTrackRefList, grp->name);
+    if (writeGroupedTrack(jw, grp->name, grp->label, fieldHash, excludeTypesHash,
+                          maxDepth, tdbRefList))
+        {
+        nonEmptyGroupCount++;
+        }
+    }
+if (nonEmptyGroupCount == 0)
+    {
+    // Catch-all for assembly hubs that don't declare groups for their tracks: add All Tracks
+    struct slRef *allTracks = sortedAllTracks(fullTrackList);
+    (void)writeGroupedTrack(jw, "allTracks", "All Tracks", fieldHash, excludeTypesHash,
+                            maxDepth, allTracks);
+    }
+jsonWriteListEnd(jw);
+jsonWriteObjectEnd(jw);
+}
+
+static char *hAssemblyDescription(char *db)
+/* Return a string containing db's description.html, or NULL if not found. */
+//#*** LIBIFY: Code lifted from hgFind.c's hgPositionsHelpHtml.
+{
+char *htmlPath = hHtmlPath(db);
+char *htmlString = NULL;
+if (htmlPath != NULL)
+    {
+    if (fileExists(htmlPath))
+	readInGulp(htmlPath, &htmlString, NULL);
+    else if (startsWith("http://" , htmlPath) ||
+	     startsWith("https://", htmlPath) ||
+	     startsWith("ftp://"  , htmlPath))
+	{
+	struct lineFile *lf = udcWrapShortLineFile(htmlPath, NULL, 256*1024);
+	htmlString = lineFileReadAll(lf);
+	lineFileClose(&lf);
+	}
+    }
+return htmlString;
+}
+
+static void getAssemblyInfo(struct cartJson *cj, struct hash *paramHash)
+/* Return useful things from dbDb (or track hub) and assembly description html (possibly NULL).
+ * If db param is NULL, use db from cart. */
+{
+char *db = cartJsonOptionalParam(paramHash, "db");
+if (db == NULL)
+    db = cartString(cj->cart, "db");
+jsonWriteString(cj->jw, "db", db);
+jsonWriteString(cj->jw, "commonName", hGenome(db));
+jsonWriteString(cj->jw, "scientificName", hScientificName(db));
+jsonWriteString(cj->jw, "dbLabel", hFreezeDate(db));
+jsonWriteString(cj->jw, "assemblyDescription", hAssemblyDescription(db));
+}
+
+static void getHasCustomTracks(struct cartJson *cj, struct hash *paramHash)
+/* Tell whether cart has custom tracks for db.  If db param is NULL, use db from cart. */
+{
+char *db = cartJsonOptionalParam(paramHash, "db");
+if (db == NULL)
+    db = cartString(cj->cart, "db");
+jsonWriteBoolean(cj->jw, "hasCustomTracks", customTracksExistDb(cj->cart, db, NULL));
+}
+
+static void getIsSpecialHost(struct cartJson *cj, struct hash *paramHash)
+/* Tell whether we're on a development host, preview, gsid etc. */
+{
+jsonWriteBoolean(cj->jw, "isPrivateHost", hIsPrivateHost());
+jsonWriteBoolean(cj->jw, "isBetaHost", hIsBetaHost());
+jsonWriteBoolean(cj->jw, "isBrowserbox", hIsBrowserbox());
+jsonWriteBoolean(cj->jw, "isPreviewHost", hIsPreviewHost());
+}
+
+static void getHasHubTable(struct cartJson *cj, struct hash *paramHash)
+/* Tell whether central database has a hub table (i.e. server can do hubs). */
+{
+jsonWriteBoolean(cj->jw, "hasHubTable", hubConnectTableExists());
+}
+
+static void setIfUnset(struct cartJson *cj, struct hash *paramHash)
+/* For each name in paramHash, if that cart variable doesn't already have a non-empty
+ * value, set it to the value. */
+{
+struct hashCookie cookie = hashFirst(paramHash);
+struct hashEl *hel;
+while ((hel = hashNext(&cookie)) != NULL)
+    {
+    if (isEmpty(cartOptionalString(cj->cart, hel->name)))
+	{
+	char *val = jsonStringVal((struct jsonElement *)(hel->val), hel->name);
+	if (val)
+	    cartSetString(cj->cart, hel->name, val);
+	}
+    }
+}
+
+static void jsonWriteValueLabel(struct jsonWrite *jw, char *value, char *label)
+/* Assuming we're already in an object, write out value and label tags & strings. */
+{
+jsonWriteString(jw, "value", value);
+jsonWriteString(jw, "label", label);
+}
+
+static void printCladeOrgDbTree(struct jsonWrite *jw)
+/* Print out the tree of clades, organisms and dbs as JSON.  Each node has value and label
+ * for menu options; clade nodes and org nodes also have children and default. */
+{
+jsonWriteListStart(jw, "cladeOrgDb");
+struct slPair *clade, *cladeOptions = hGetCladeOptions();
+struct dbDb *centralDbDbList = hDbDbList();
+for (clade = cladeOptions;  clade != NULL;  clade = clade->next)
+    {
+    jsonWriteObjectStart(jw, NULL);
+    jsonWriteValueLabel(jw, clade->name, clade->val);
+    jsonWriteListStart(jw, "children");
+    struct slPair *org, *orgOptions = hGetGenomeOptionsForClade(clade->name);
+    for (org = orgOptions;  org != NULL;  org = org->next)
+        {
+        jsonWriteObjectStart(jw, NULL);
+        jsonWriteValueLabel(jw, org->name, org->val);
+        jsonWriteListStart(jw, "children");
+        struct dbDb *dbDb, *dbDbList;
+        if (isHubTrack(org->name))
+            dbDbList = trackHubGetDbDbs(clade->name);
+        else
+            dbDbList = centralDbDbList;
+        for (dbDb = dbDbList;  dbDb != NULL;  dbDb = dbDb->next)
+            {
+            if (sameString(org->name, dbDb->genome))
+                {
+                jsonWriteObjectStart(jw, NULL);
+                jsonWriteValueLabel(jw, dbDb->name, dbDb->description);
+                jsonWriteString(jw, "defaultPos", dbDb->defaultPos);
+                jsonWriteObjectEnd(jw);
+                }
+            }
+        jsonWriteListEnd(jw);   // children (dbs)
+        jsonWriteString(jw, "default", trimSpaces(hDefaultDbForGenome(org->name)));
+        jsonWriteObjectEnd(jw); // org
+        }
+    jsonWriteListEnd(jw);   // children (orgs)
+    jsonWriteString(jw, "default", trimSpaces(hDefaultGenomeForClade(clade->name)));
+    jsonWriteObjectEnd(jw); // clade
+    }
+jsonWriteListEnd(jw);
+}
+
+static void getCladeOrgDbPos(struct cartJson *cj, struct hash *paramHash)
+/* Get cart's current clade, org, db, position and geneSuggest track. */
+{
+jsonWriteObjectStart(cj->jw, "cladeOrgDb");
+printCladeOrgDbTree(cj->jw);
+char *db = cartString(cj->cart, "db");
+jsonWriteString(cj->jw, "db", db);
+char *org = cartUsualString(cj->cart, "org", hGenome(db));
+jsonWriteString(cj->jw, "org", org);
+char *clade = cartUsualString(cj->cart, "clade", hClade(org));
+jsonWriteString(cj->jw, "clade", clade);
+jsonWriteObjectEnd(cj->jw);
+char *position = cartOptionalString(cj->cart, "position");
+if (isEmpty(position))
+    position = hDefaultPos(db);
+jsonWriteString(cj->jw, "position", position);
+printGeneSuggestTrack(cj, db);
+}
+
+static void getStaticHtml(struct cartJson *cj, struct hash *paramHash)
+/* Read HTML text from a relative path under browser.documentRoot and
+ * write it as an encoded JSON string */
+{
+char *tag = cartJsonOptionalParam(paramHash, "tag");
+if (isEmpty(tag))
+    tag = "html";
+char *file = cartJsonRequiredParam(paramHash, "file", cj->jw, "getStaticHtml");
+char *html = hFileContentsOrWarning(file);
+jsonWriteString(cj->jw, tag, html);
+}
+
+void cartJsonRegisterHandler(struct cartJson *cj, char *command, CartJsonHandler *handler)
+/* Associate handler with command; when javascript sends command, handler will be executed. */
+{
+hashAdd(cj->handlerHash, command, handler);
+}
+
+struct cartJson *cartJsonNew(struct cart *cart)
+/* Allocate and return a cartJson object with default handlers.
+ * cart must have "db" set already. */
+{
+struct cartJson *cj;
+AllocVar(cj);
+cj->handlerHash = hashNew(0);
+cj->jw = jsonWriteNew();
+cj->cart = cart;
+cartJsonRegisterHandler(cj, "getCladeOrgDbPos", getCladeOrgDbPos);
+cartJsonRegisterHandler(cj, "changePosition", changePositionHandler);
+cartJsonRegisterHandler(cj, "get", getVar);
+cartJsonRegisterHandler(cj, "getGroupedTrackDb", cartJsonGetGroupedTrackDb);
+cartJsonRegisterHandler(cj, "getAssemblyInfo", getAssemblyInfo);
+cartJsonRegisterHandler(cj, "getGeneSuggestTrack", getGeneSuggestTrack);
+cartJsonRegisterHandler(cj, "getHasCustomTracks", getHasCustomTracks);
+cartJsonRegisterHandler(cj, "getIsSpecialHost", getIsSpecialHost);
+cartJsonRegisterHandler(cj, "getHasHubTable", getHasHubTable);
+cartJsonRegisterHandler(cj, "setIfUnset", setIfUnset);
+cartJsonRegisterHandler(cj, "getStaticHtml", getStaticHtml);
+return cj;
+}
+
+void cartJsonFree(struct cartJson **pCj)
+/* Close **pCj's contents and nullify *pCj. */
+{
+if (*pCj == NULL)
+    return;
+struct cartJson *cj = *pCj;
+jsonWriteFree(&cj->jw);
+hashFree(&cj->handlerHash);
+freez(pCj);
+}
+
+static void doOneCommand(struct cartJson *cj, char *command,
+                         struct jsonElement *paramObject)
+/* Dispatch command by name, checking for required parameters. */
+{
+CartJsonHandler *handler = hashFindVal(cj->handlerHash, command);
+if (handler)
+    {
+    struct hash *paramHash = jsonObjectVal(paramObject, command);
+    handler(cj, paramHash);
+    }
+else
+    {
+    jsonWriteStringf(cj->jw, "error",
+		     "cartJson: unrecognized command '%s'\"", command);
+    return;
+    }
+}
+
+static int commandCmp(const void *pa, const void *pb)
+/* Comparison function to put "change" commands ahead of other commands. */
+{
+struct slPair *cmdA = *((struct slPair **)pa);
+struct slPair *cmdB = *((struct slPair **)pb);
+boolean aIsChange = startsWith("change", cmdA->name);
+boolean bIsChange = startsWith("change", cmdB->name);
+if (aIsChange && !bIsChange)
+    return -1;
+if (!aIsChange && bIsChange)
+    return 1;
+return strcmp(cmdA->name, cmdB->name);
+}
+
+// Accumulate warnings so they can be JSON-ified:
+static struct dyString *dyWarn = NULL;
+
+static void cartJsonVaWarn(char *format, va_list args)
+/* Save warnings for later. */
+{
+dyStringVaPrintf(dyWarn, format, args);
+}
+
+static void cartJsonPrintWarnings(struct jsonWrite *jw)
+/* If there are warnings, write them out as JSON: */
+{
+if (dyWarn && dyStringLen(dyWarn) > 0)
+    jsonWriteString(jw, "warning", dyWarn->string);
+}
+
+static void cartJsonAbort()
+/* Print whatever warnings we have accumulated and exit. */
+{
+if (dyWarn)
+    puts(dyWarn->string);
+exit(0);
+}
+
+static void cartJsonPushErrHandlers()
+/* Push warn and abort handlers for errAbort. */
+{
+if (dyWarn == NULL)
+    dyWarn = dyStringNew(0);
+else
+    dyStringClear(dyWarn);
+pushWarnHandler(cartJsonVaWarn);
+pushAbortHandler(cartJsonAbort);
+}
+
+static void cartJsonPopErrHandlers()
+/* Pop warn and abort handlers for errAbort. */
+{
+popWarnHandler();
+popAbortHandler();
+}
+
+void cartJsonExecute(struct cartJson *cj)
+/* Get commands from cgi, print Content-type, execute commands, print results as JSON. */
+{
+cartJsonPushErrHandlers();
+puts("Content-Type:text/javascript\n");
+
+// Initialize response JSON object:
+jsonWriteObjectStart(cj->jw, NULL);
+// Always send back hgsid:
+jsonWriteString(cj->jw, cartSessionVarName(), cartSessionId(cj->cart));
+
+char *commandJson = cgiOptionalString(CARTJSON_COMMAND);
+if (commandJson)
+    {
+    struct errCatch *errCatch = errCatchNew();
+    if (errCatchStart(errCatch))
+        {
+        struct jsonElement *commandObj = jsonParse(commandJson);
+        struct hash *commandHash = jsonObjectVal(commandObj, "commandObj");
+        // change* commands need to go first!  Really we need an ordered map type here...
+        // for now, just make a list and sort to put change commands at the front.
+        struct slPair *commandList = NULL, *cmd;
+        struct hashCookie cookie = hashFirst(commandHash);
+        struct hashEl *hel;
+        while ((hel = hashNext(&cookie)) != NULL)
+            slAddHead(&commandList, slPairNew(hel->name, hel->val));
+        slSort(&commandList, commandCmp);
+        for (cmd = commandList;  cmd != NULL;  cmd = cmd->next)
+            doOneCommand(cj, cmd->name, (struct jsonElement *)cmd->val);
+        }
+    errCatchEnd(errCatch);
+    if (errCatch->gotError)
+        {
+        jsonWritePopToLevel(cj->jw, 1);
+        jsonWriteString(cj->jw, "error", errCatch->message->string);
+        }
+    errCatchFree(&errCatch);
+    }
+
+cartJsonPrintWarnings(cj->jw);
+jsonWriteObjectEnd(cj->jw);
+puts(cj->jw->dy->string);
+cartJsonPopErrHandlers();
+}