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(); +}