748b06ac95ff2a3957be3845bd3594984e3cc3cf
chmalee
  Wed Aug 17 19:21:48 2022 -0700
Rename test cgi to official name. Always search everything, only show
categories that have matches in the result list. Add /search endpoint to
hubApi, add code to search help docs in hgSuggest but don't call it yet
in autoComplete.js. Minor fixups so search result links work correctly.

Fixing up old programs that call hgPositionsFind

diff --git src/hg/hubApi/hubApi.c src/hg/hubApi/hubApi.c
index 9388bcc..46d2308 100644
--- src/hg/hubApi/hubApi.c
+++ src/hg/hubApi/hubApi.c
@@ -1,1599 +1,1601 @@
 /* hubApi - access mechanism to hub data resources. */
 #include "dataApi.h"
 #include "botDelay.h"
 #include "jsHelper.h"
 
 /*
 +------------------+------------------+------+-----+---------+-------+
 | Field            | Type             | Null | Key | Default | Extra |
 +------------------+------------------+------+-----+---------+-------+
 | hubUrl           | longblob         | NO   | PRI | NULL    |       |
 | shortLabel       | varchar(255)     | NO   |     | NULL    |       |
 | longLabel        | varchar(255)     | NO   |     | NULL    |       |
 | registrationTime | varchar(255)     | NO   |     | NULL    |       |
 | dbCount          | int(10) unsigned | NO   |     | NULL    |       |
 | dbList           | blob             | YES  |     | NULL    |       |
 | descriptionUrl   | longblob         | YES  |     | NULL    |       |
 +------------------+------------------+------+-----+---------+-------+
 */
 
 /* Global Variables for all modules */
 
 static int maxItemLimit = 1000000;   /* maximum of 1,000,000 items returned */
 int maxItemsOutput = 1000000;   /* can be set in URL maxItemsOutput=N */
 boolean reachedMaxItems = FALSE;	/* during getData, signal to return */
 long long itemsReturned = 0;	/* for getData functions, number of items returned */
 /* for debugging purpose, current bot delay value */
 int botDelay = 0;
 boolean debug = FALSE;	/* can be set in URL debug=1, to turn off: debug=0 */
 #define delayFraction	0.03
 
 /* default is to list all trackDb entries, composite containers too.
  * This option will limit to only the actual track entries with data
  */
 boolean trackLeavesOnly = FALSE;  /* set by CGI parameter 'trackLeavesOnly' */
 /* this selects output type 'arrays', where the default type is: objects */
 boolean jsonOutputArrays = FALSE; /* set by CGI parameter 'jsonOutputArrays' */
 
 boolean measureTiming = FALSE;	/* set by CGI parameters */
 
 /* downloadUrl for use in error exits when reachedMaxItems */
 struct dyString *downloadUrl = NULL;
 
 /* valid argument listings to verify extraneous arguments */
 char *argListPublicHubs[] = { NULL };
 char *argListUcscGenomes[] = { NULL };
 char *argListHubGenomes[] = { argHubUrl, NULL };
 char *argListTracks[] = { argGenome, argHubUrl, argTrackLeavesOnly, NULL };
 char *argListChromosomes[] = { argGenome, argHubUrl, argTrack, NULL };
 char *argListSchema[] = { argGenome, argHubUrl, argTrack, NULL };
 char *argGetDataTrack[] = { argGenome, argHubUrl, argTrack, argChrom, argStart, argEnd, argMaxItemsOutput, argJsonOutputArrays, NULL };
 char *argGetDataSequence[] = { argGenome, argHubUrl, argTrack, argChrom, argStart, argEnd, NULL };
+char *argSearch[] = {argSearchTerm, argGenome, argHubUrl, argCategories, NULL};
 
 /* Global only to this one source file */
 static struct cart *cart;             /* CGI and other variables */
 static struct hash *oldVars = NULL;
 static struct hash *trackCounter = NULL;
 static long totalTracks = 0;
 static boolean allTrackSettings = FALSE;	/* checkbox setting */
 static char **shortLabels = NULL;	/* public hub short labels in array */
 static int publicHubCount = 0;
 static char *defaultHub = "Synonymous Constraint";
 static char *defaultDb = "ce11";
 long enteredMainTime = 0;	/* will become = clock1000() on entry */
 		/* to allow calculation of when to bail out, taking too long */
 static long timeOutSeconds = 100;
 static boolean timedOut = FALSE;
 static char *urlPrefix = "";	/* initalized to support self references */
 
 /* supportedTypes will be initialized to a known supported set */
 struct slName *supportedTypes = NULL;
 
 static void initSupportedTypes()
 /* initalize the list of supported track types */
 {
 struct slName *el = newSlName("bed");
 slAddHead(&supportedTypes, el);
 el = newSlName("wig");
 slAddHead(&supportedTypes, el);
 el = newSlName("wigMaf");
 slAddHead(&supportedTypes, el);
 el = newSlName("broadPeak");
 slAddHead(&supportedTypes, el);
 el = newSlName("narrowPeak");
 slAddHead(&supportedTypes, el);
 el = newSlName("bigBed");
 slAddHead(&supportedTypes, el);
 el = newSlName("bigWig");
 slAddHead(&supportedTypes, el);
 el = newSlName("bigLolly");
 slAddHead(&supportedTypes, el);
 el = newSlName("bigNarrowPeak");
 slAddHead(&supportedTypes, el);
 el = newSlName("bigGenePred");
 slAddHead(&supportedTypes, el);
 el = newSlName("genePred");
 slAddHead(&supportedTypes, el);
 el = newSlName("psl");
 slAddHead(&supportedTypes, el);
 el = newSlName("rmsk");
 slAddHead(&supportedTypes, el);
 el = newSlName("bigRmsk");
 slAddHead(&supportedTypes, el);
 el = newSlName("bigPsl");
 slAddHead(&supportedTypes, el);
 el = newSlName("altGraphX");
 slAddHead(&supportedTypes, el);
 el = newSlName("barChart");
 slAddHead(&supportedTypes, el);
 el = newSlName("chain");
 slAddHead(&supportedTypes, el);
 el = newSlName("ctgPos");
 slAddHead(&supportedTypes, el);
 el = newSlName("expRatio");
 slAddHead(&supportedTypes, el);
 el = newSlName("factorSource");
 slAddHead(&supportedTypes, el);
 el = newSlName("gvf");
 slAddHead(&supportedTypes, el);
 el = newSlName("interact");
 slAddHead(&supportedTypes, el);
 el = newSlName("netAlign");
 slAddHead(&supportedTypes, el);
 el = newSlName("peptideMapping");
 slAddHead(&supportedTypes, el);
 el = newSlName("pgSnp");
 slAddHead(&supportedTypes, el);
 el = newSlName("bigBarChart");
 slAddHead(&supportedTypes, el);
 el = newSlName("bigInteract");
 slAddHead(&supportedTypes, el);
 el = newSlName("clonePos");
 slAddHead(&supportedTypes, el);
 el = newSlName("bigMaf");
 // slAddHead(&supportedTypes, el);
 // el = newSlName("bigChain");
 // slAddHead(&supportedTypes, el);
 slNameSort(&supportedTypes);
 }	/*	static void initSupportedTypes()	*/
 
 static int publicHubCmpCase(const void *va, const void *vb)
 /* Compare two shortLabels, ignore case. */
 {
 const struct hubPublic *a = *((struct hubPublic **)va);
 const struct hubPublic *b = *((struct hubPublic **)vb);
 return strcasecmp(a->shortLabel, b->shortLabel);
 }
 
 static void publicHubSortCase(struct hubPublic **pList)
 /* Sort slName list, ignore case. */
 {
 slSort(pList, publicHubCmpCase);
 }
 
 struct hubPublic *hubPublicDbLoadAll()
 /* read entire hubPublic table in hgcentral and return resulting list */
 {
 char query[1024];
 struct hubPublic *list = NULL;
 struct sqlConnection *conn = hConnectCentral();
 sqlSafef(query, sizeof(query), "select * from %s", hubPublicTableName());
 struct sqlResult *sr = sqlGetResult(conn, query);
 char **row;
 while ((row = sqlNextRow(sr)) != NULL)
     {
     struct hubPublic *el = hubPublicLoad(row);
     slAddHead(&list, el);
     }
 sqlFreeResult(&sr);
 hDisconnectCentral(&conn);
 publicHubSortCase(&list);
 int listSize = slCount(list);
 AllocArray(shortLabels, listSize);
 struct hubPublic *el = list;
 int i = 0;
 for ( ; el != NULL; el = el->next )
     {
     shortLabels[i++] = el->shortLabel;
     ++publicHubCount;
     }
 return list;
 }
 
 static boolean timeOutReached()
 /* see if the timeout has been reached to determine if an exit
  *   is appropriate at this time
  */
 {
 long nowTime = clock1000();
 timedOut = FALSE;
 if ((nowTime - enteredMainTime) > (1000 * timeOutSeconds))
     timedOut= TRUE;
 return timedOut;
 }
 
 static void hashCountTrack(struct trackDb *tdb, struct hash *countTracks)
 /* this is counting up track types into the hash countTracks */
 {
 char *stripType = cloneString(tdb->type);
 if (startsWith("chain ", tdb->type))
     stripType = cloneString("chain");
 else if (startsWith("netAlign ", tdb->type))
     stripType = cloneString("netAlign");
 else if (startsWith("genePred ", tdb->type))
     stripType = cloneString("genePred");
 else if (startsWith("bigWig ", tdb->type))
     stripType = cloneString("bigWig");
 else if (startsWith("wigMaf ", tdb->type))
     stripType = cloneString("wigMaf");
 else if (startsWith("wig ", tdb->type))
     stripType = cloneString("wig");
 else
     stripType = cloneString(tdb->type);
 boolean compositeContainer = tdbIsComposite(tdb);
 boolean compositeView = tdbIsCompositeView(tdb);
 boolean superChild = tdbIsSuperTrackChild(tdb);
 if (compositeContainer)
     hashIncInt(countTracks, "composite container");
 else if (compositeView)
     hashIncInt(countTracks, "composite view");
 else if (superChild)
     {
     hashIncInt(countTracks, "superTrack child");
     hashIncInt(countTracks, stripType);
     hashIncInt(countTracks, "track count");
     }
 else if (isEmpty(tdb->type))
     hashIncInt(countTracks, "no type specified");
 else
     {
     hashIncInt(countTracks, stripType);
     hashIncInt(countTracks, "track count");
     }
 freeMem(stripType);
 }
 
 static void sampleUrl(struct trackHub *hub, char *db, struct trackDb *tdb, char *errorString)
 /* print out a sample getData URL */
 {
 char errorPrint[2048];
 errorPrint[0] = 0;
 
 if (isNotEmpty(errorString))
     {
     safef(errorPrint, sizeof(errorPrint), " <font color='red'>ERROR: %s</font>", errorString);
     }
 
 boolean superChild = tdbIsSuperTrackChild(tdb);
 char *genome = NULL;
 if (hub)
     genome = hub->genomeList->name;
 
 struct dyString *extraDyFlags = dyStringNew(128);
 if (debug)
     dyStringAppend(extraDyFlags, ";debug=1");
 if (jsonOutputArrays)
     dyStringAppend(extraDyFlags, ";jsonOutputArrays=1");
 char *extraFlags = dyStringCannibalize(&extraDyFlags);
 
 if (protectedTrack(tdb, tdb->track))
     hPrintf("<li>%s : %s &lt;protected data&gt;</li>\n", tdb->track, tdb->type);
 else if (db)
     {
     if (hub)
 	{
 	char urlReference[2048];
 	safef(urlReference,	sizeof(urlReference), " <a href='%s/getData/track?hubUrl=%s;genome=%s;track=%s;maxItemsOutput=5%s' target=_blank>(sample data)%s</a>\n", urlPrefix, hub->url, genome, tdb->track, extraFlags, errorPrint);
 
 	if (tdb->parent)
 	    hPrintf("<li><b>%s</b>: %s subtrack of parent: %s%s</li>\n", tdb->track, tdb->type, tdb->parent->track, urlReference);
 	else
 	    hPrintf("<li><b>%s</b>: %s%s</li>\n", tdb->track, tdb->type, urlReference);
 	}
     else
 	{
 	char urlReference[2048];
 	safef(urlReference, sizeof(urlReference), " <a href='%s/getData/track?genome=%s;track=%s;maxItemsOutput=5%s' target=_blank>(sample data)%s</a>\n", urlPrefix, db, tdb->track, extraFlags, errorPrint);
 
 	if (superChild)
 	    hPrintf("<li><b>%s</b>: %s superTrack child of parent: %s%s</li>\n", tdb->track, tdb->type, tdb->parent->track, urlReference);
 	else if (tdb->parent)
 	    hPrintf("<li><b>%s</b>: %s subtrack of parent: %s%s</li>\n", tdb->track, tdb->type, tdb->parent->track, urlReference);
 	else
 	    hPrintf("<li><b>%s</b>: %s%s</li>\n", tdb->track, tdb->type, urlReference );
 	}
     }
 else if (hub)
     {
     char urlReference[2048];
     safef(urlReference, sizeof(urlReference), " <a href='%s/getData/track?hubUrl=%s;genome=%s;track=%s;maxItemsOutput=5%s' target=_blank>(sample data)%s</a>\n", urlPrefix, hub->url, genome, tdb->track, extraFlags, errorPrint);
 
     if (tdb->parent)
 	hPrintf("<li><b>%s</b>: %s subtrack of parent: %s%s</li>\n", tdb->track, tdb->type, tdb->parent->track, urlReference);
     else
 	hPrintf("<li><b>%s</b>: %s%s</li>\n", tdb->track, tdb->type, urlReference);
     }
 else
     hPrintf("<li>%s : %s not db hub track ?</li>\n", tdb->track, tdb->type);
 }
 
 static void hubSampleUrl(struct trackHub *hub, struct trackDb *tdb,
     long chromCount, long itemCount, char *genome, char *errorString)
 {
 struct dyString *extraDyFlags = dyStringNew(128);
 if (debug)
     dyStringAppend(extraDyFlags, ";debug=1");
 if (jsonOutputArrays)
     dyStringAppend(extraDyFlags, ";jsonOutputArrays=1");
 char *extraFlags = dyStringCannibalize(&extraDyFlags);
 
 char errorPrint[2048];
 errorPrint[0] = 0;
 
 if (isNotEmpty(errorString))
     {
     safef(errorPrint, sizeof(errorPrint), " : <font color='red'>ERROR: %s</font>", errorString);
     }
 
 char countsMessage[512];
 countsMessage[0] = 0;
 if (chromCount > 0 || itemCount > 0)
     {
     if (allowedBigBedType(tdb->type))
         safef(countsMessage, sizeof(countsMessage), " : %ld chroms : %ld item count ", chromCount, itemCount);
     else if (startsWithWord("bigWig", tdb->type))
         safef(countsMessage, sizeof(countsMessage), " : %ld chroms : %ld bases covered ", chromCount, itemCount);
     else
         safef(countsMessage, sizeof(countsMessage), " : %ld chroms : %ld count ", chromCount, itemCount);
     }
 
 if (protectedTrack(tdb, tdb->track))
     hPrintf("    <li><b>%s</b>: %s protected data</li>\n", tdb->track, tdb->type);
 else if (isSupportedType(tdb->type))
     {
 	char urlReference[2048];
 	safef(urlReference, sizeof(urlReference), "<a href='%s/getData/track?hubUrl=%s;genome=%s;track=%s;maxItemsOutput=5%s' target=_blank>(sample data)%s</a>\n", urlPrefix, hub->url, genome, tdb->track, extraFlags, errorPrint);
 
 	if (allowedBigBedType(tdb->type))
             hPrintf("    <li><b>%s</b>: %s%s%s</li>\n", tdb->track, tdb->type, countsMessage, urlReference);
         else if (startsWithWord("bigWig", tdb->type))
             hPrintf("    <li><b>%s</b>: %s%s%s</li>\n", tdb->track, tdb->type, countsMessage, urlReference);
         else
             hPrintf("    <li><b>%s</b>: %s%s%s</li>\n", tdb->track, tdb->type, countsMessage, urlReference);
     }
 else
     {
         if (allowedBigBedType(tdb->type))
             hPrintf("    <li><b>%s</b>: %s%s</li>\n", tdb->track, tdb->type, countsMessage);
         else if (startsWithWord("bigWig", tdb->type))
             hPrintf("    <li><b>%s</b>: %s%s</li>\n", tdb->track, tdb->type, countsMessage);
         else
             hPrintf("    <li><b>%s</b>: %s%s</li>\n", tdb->track, tdb->type, countsMessage);
     }
 }	/* static void hubSampleUrl(struct trackHub *hub, struct trackDb *tdb,
 	 * long chromCount, long itemCount, char *genome)
 	 */
 
 static void bbiLargestChrom(struct bbiChromInfo *chromList, char **chromName,
     unsigned *chromSize)
 /* find largest chromosome name and size in the chromList */
 {
 if (chromName && chromSize)
     {
     *chromSize = 0;
     char *returnName = NULL;
     struct bbiChromInfo *el;
     for (el = chromList; el; el = el->next)
 	{
 	if (el->size > *chromSize)
 	    {
 	    *chromSize = el->size;
 	    returnName = el->name;
 	    }
 	}
     if (chromSize > 0)
 	*chromName = cloneString(returnName);
     }
 }
 
 static int bbiBriefMeasure(char *type, char *bigDataUrl, char *bigDataIndex, long *chromCount, long *itemCount, struct dyString *errors, char **chromName, unsigned *chromSize)
 /* check a bigDataUrl to find chrom count and item count, return
  *   name of largest chrom and its size
  */
 {
 int retVal = 0;
 *chromCount = 0;
 *itemCount = 0;
 struct errCatch *errCatch = errCatchNew();
 if (errCatchStart(errCatch))
     {
     if (startsWithWord("bigNarrowPeak", type)
             || startsWithWord("bigBed", type)
             || startsWithWord("bigGenePred", type)
             || startsWithWord("bigPsl", type)
             || startsWithWord("bigChain", type)
             || startsWithWord("bigMaf", type)
             || startsWithWord("bigBarChart", type)
             || startsWithWord("bigInteract", type))
         {
         struct bbiFile *bbi = NULL;
         bbi = bigBedFileOpen(bigDataUrl);
         struct bbiChromInfo *chromList = bbiChromList(bbi);
         *chromCount = slCount(chromList);
         *itemCount = bigBedItemCount(bbi);
         bbiLargestChrom(chromList, chromName, chromSize);
         bbiChromInfoFreeList(&chromList);
         bbiFileClose(&bbi);
         }
     else if (startsWithWord("bigWig", type))
         {
         struct bbiFile *bwf = bigWigFileOpen(bigDataUrl);
         struct bbiChromInfo *chromList = bbiChromList(bwf);
         struct bbiSummaryElement sum = bbiTotalSummary(bwf);
         *chromCount = slCount(chromList);
         *itemCount = sum.validCount;
         bbiLargestChrom(chromList, chromName, chromSize);
         bbiChromInfoFreeList(&chromList);
         bbiFileClose(&bwf);
         }
     else if (startsWithWord("vcfTabix", type))
         {
         struct vcfFile *vcf = vcfTabixFileAndIndexMayOpen(bigDataUrl, bigDataIndex, NULL, 0, 0, 1, 1);
         if (vcf == NULL)
 	    {
 	    dyStringPrintf(errors, "Could not open %s and/or its tabix index (.tbi) file.  See http://genome.ucsc.edu/goldenPath/help/vcf.html", bigDataUrl);
             retVal = 1;
 	    }
         else
 	    vcfFileFree(&vcf);
         }
     else if (startsWithWord("bam", type))
         {
 	bamFileAndIndexMustExist(bigDataUrl, bigDataIndex);
         }
     else if (startsWithWord("longTabix", type))
         {
 	struct bedTabixFile *btf = bedTabixFileMayOpen(bigDataUrl, NULL, 0, 0);
 	if (btf == NULL)
 	    {
 	    dyStringPrintf(errors, "Couldn't open %s and/or its tabix index (.tbi) file.", bigDataUrl);
             retVal = 1;
 	    }
 	else
 	    bedTabixFileClose(&btf);
         }
 #ifdef USE_HAL
     else if (startsWithWord("halSnake", type))
         {
 	char *errString;
 	int handle = halOpenLOD(bigDataUrl, &errString);
 	if (handle < 0)
 	    {
 	    dyStringPrintf(errors, "HAL open error: %s", errString);
             retVal = 1;
 	    }
 	if (halClose(handle, &errString) < 0)
 	    {
 	    dyStringPrintf(errors, "HAL close error: %s", errString);
 	    retVal = 1;
 	    }
         }
 #endif
     else
         {
 	dyStringPrintf(errors, "unrecognized type %s", type);
         retVal = 1;
         }
 
     }
 errCatchEnd(errCatch);
 if (errCatch->gotError)
     {
     retVal = 1;
     dyStringPrintf(errors, "%s", errCatch->message->string);
     }
 errCatchFree(&errCatch);
 
 return retVal;
 }	/* static int bbiBriefMeasure() */
 
 static void hubSubTracks(struct trackHub *hub, char *db, struct trackDb *tdb,
     struct hash *countTracks,  long chromCount, long itemCount,
     char *chromName, unsigned chromSize, char *genome, char *errorString)
 /* tdb has subtracks, show only subTracks, no details */
 {
 hPrintf("    <li><ul>\n");
 if (debug)
     {
     hPrintf("    <li>subtracks for '%s' db: '%s'</li>\n", tdb->track, db);
     hPrintf("    <li>chrom: '%s' size: %u</li>\n", chromName, chromSize);
     }
 if (tdb->subtracks)
     {
     struct trackDb *tdbEl = NULL;
     for (tdbEl = tdb->subtracks; tdbEl; tdbEl = tdbEl->next)
 	{
 	boolean compositeContainer = tdbIsComposite(tdbEl);
 	boolean compositeView = tdbIsCompositeView(tdbEl);
 	if (! (compositeContainer || compositeView) )
 	    {
             char *bigDataIndex = NULL;
             char *relIdxUrl = trackDbSetting(tdbEl, "bigDataIndex");
             if (relIdxUrl != NULL)
                 bigDataIndex = trackHubRelativeUrl(hub->genomeList->trackDbFile, relIdxUrl);
             char *bigDataUrl = trackDbSetting(tdbEl, "bigDataUrl");
             char *longName = NULL;
             unsigned longSize = 0;
             struct dyString *errors = dyStringNew(1024);
             (void) bbiBriefMeasure(tdbEl->type, bigDataUrl, bigDataIndex, &chromCount, &itemCount, errors, &longName, &longSize);
             chromSize = longSize;
             chromName = longName;
 	    }
         if (tdbIsCompositeView(tdbEl))
 	    hPrintf("<li><b>%s</b>: %s : composite view of parent: %s</li>\n", tdbEl->track, tdbEl->type, tdbEl->parent->track);
 	else
 	    {
 	    if (isSupportedType(tdbEl->type))
 		hubSampleUrl(hub, tdbEl, chromCount, itemCount, genome, errorString);
 	    else
 		hPrintf("<li><b>%s</b>: %s : subtrack of parent: %s</li>\n", tdbEl->track, tdbEl->type, tdbEl->parent->track);
 	    }
 	hashCountTrack(tdbEl, countTracks);
         if (tdbEl->subtracks)
 	    hubSubTracks(hub, db, tdbEl, countTracks, chromCount, itemCount, chromName, chromSize, genome, errorString);
 	}
     }
 hPrintf("    </ul></li>\n");
 }	/* hubSubTracks() */
 
 static void showSubTracks(struct trackHub *hub, char *db, struct trackDb *tdb, struct hash *countTracks,
     char *chromName, unsigned chromSize, char *errorString)
 /* tdb has subtracks, show only subTracks, no details */
 {
 hPrintf("    <li><ul>\n");
 if (debug)
     hPrintf("    <li>subtracks for '%s' db: '%s'</li>\n", tdb->track, db);
 if (tdb->subtracks)
     {
     struct trackDb *tdbEl = NULL;
     for (tdbEl = tdb->subtracks; tdbEl; tdbEl = tdbEl->next)
 	{
         if (tdbIsCompositeView(tdbEl))
 	    hPrintf("<li><b>%s</b>: %s : composite view of parent: %s</li>\n", tdbEl->track, tdbEl->type, tdbEl->parent->track);
 	else
 	    {
 	    if (isSupportedType(tdbEl->type))
 		sampleUrl(hub, db, tdbEl, errorString);
 	    else
 		hPrintf("<li><b>%s</b>: %s : subtrack of parent: %s</li>\n", tdbEl->track, tdbEl->type, tdbEl->parent->track);
 	    }
 	hashCountTrack(tdbEl, countTracks);
         if (tdbEl->subtracks)
 	    showSubTracks(hub, db, tdbEl, countTracks, chromName, chromSize, errorString);
 	}
     }
 hPrintf("    </ul></li>\n");
 }
 
 static void trackSettings(struct trackDb *tdb, struct hash *countTracks)
 /* process the settingsHash for a trackDb, recursive when subtracks */
 {
 hPrintf("    <li><ul>\n");
 boolean protectedData = protectedTrack(tdb, tdb->track);
 struct hashEl *hel;
 struct hashCookie hc = hashFirst(tdb->settingsHash);
 while ((hel = hashNext(&hc)) != NULL)
     {
     if (sameWord("track", hel->name))
 	continue;	// already output in header
     if (sameWord("tableBrowser", hel->name)
 		&& startsWithWord("off", (char*)hel->val))
 	hPrintf("    <li><b>protectedData</b>: 'true'</li>\n");
     else if (protectedData && sameWord("bigDataUrl", hel->name))
 	hPrintf("    <li><b>bigDataUrl</b>: &lt;protected data&gt;</li>\n");
     else if (isEmpty((char *)hel->val))
 	hPrintf("    <li><b>%s</b>: &lt;empty&gt;</li>\n", hel->name);
     else
 	hPrintf("    <li><b>%s</b>: '%s'</li>\n", hel->name, (char *)hel->val);
     }
 if (tdb->subtracks)
     {
     struct trackDb *tdbEl = NULL;
     if (debug)
 	hPrintf("   <li>has %d subtrack(s)</li>\n", slCount(tdb->subtracks));
     for (tdbEl = tdb->subtracks; tdbEl; tdbEl = tdbEl->next)
 	{
         hPrintf("<li>subtrack: %s of parent: %s : type: '%s' (TBD: sample data)</li>\n", tdbEl->track, tdbEl->parent->track, tdbEl->type);
 	hashCountTrack(tdbEl, countTracks);
 	trackSettings(tdbEl, countTracks);
 	}
     }
 hPrintf("    </ul></li>\n");
 }
 
 static void hubCountOneTdb(struct trackHub *hub, char *db, struct trackDb *tdb,
     char *bigDataIndex, struct hash *countTracks, char *chromName,
     unsigned chromSize, char *genome)
 {
 char *bigDataUrl = trackDbSetting(tdb, "bigDataUrl");
 boolean compositeContainer = tdbIsComposite(tdb);
 boolean compositeView = tdbIsCompositeView(tdb);
 boolean superChild = tdbIsSuperTrackChild(tdb);
 boolean depthSearch = cartUsualBoolean(cart, "depthSearch", FALSE);
 hashCountTrack(tdb, countTracks);
 
 long chromCount = 0;
 long itemCount = 0;
 
 struct dyString *errors = dyStringNew(1024);
 
 /* if given a chromSize, it belongs to a UCSC db and this is *not* an
  *   assembly hub, otherwise, look up a chrom and size in the bbi file
  */
 if (! (compositeContainer || compositeView) )
     {
     if (chromSize < 1 || depthSearch)
 	{
 	char *longName = NULL;
 	unsigned longSize = 0;
         (void) bbiBriefMeasure(tdb->type, bigDataUrl, bigDataIndex, &chromCount, &itemCount, errors, &longName, &longSize);
 	chromSize = longSize;
 	chromName = longName;
 	}
     }
 
 if (depthSearch && bigDataUrl)
     {
     if (isSupportedType(tdb->type))
 	    hubSampleUrl(hub, tdb, chromCount, itemCount, genome, errors->string);
     }
 else
     {
     if (compositeContainer)
         hPrintf("    <li><b>%s</b>: %s : composite track container has %d subtracks</li>\n", tdb->track, tdb->type, slCount(tdb->subtracks));
     else if (compositeView)
         hPrintf("    <li><b>%s</b>: %s : composite view of parent: %s</li>\n", tdb->track, tdb->type, tdb->parent->track);
     else if (superChild)
 	{
 	if (isSupportedType(tdb->type))
 	    hubSampleUrl(hub, tdb, chromCount, itemCount, genome,  errors->string);
 	else
 	    hPrintf("    <li><b>%s</b>: %s : superTrack child of parent: %s</li>\n", tdb->track, tdb->type, tdb->parent->track);
 	}
     else if (! depthSearch && bigDataUrl)
 	{
         if (isSupportedType(tdb->type))
 	    {
 	    hubSampleUrl(hub, tdb, chromCount, itemCount, genome, errors->string);
 	    }
 	}
     else
 	{
         if (isSupportedType(tdb->type))
 	    {
 	    hubSampleUrl(hub, tdb, chromCount, itemCount, genome, errors->string);
 	    }
 	else
 	    hPrintf("    <li><b>%s</b>: %s (what is this)</li>\n", tdb->track, tdb->type);
         }
     }
 if (allTrackSettings)
     {
     hPrintf("    <li><ul>\n");
     trackSettings(tdb, countTracks); /* show all settings */
     hPrintf("    </ul></li>\n");
     }
 else if (tdb->subtracks)
     {
     hubSubTracks(hub, db, tdb, countTracks, chromCount, itemCount, chromName, chromSize, genome, errors->string);
     }
 return;
 }	/*	static void hubCountOneTdb(char *db, struct trackDb *tdb,
 	 *	char *bigDataIndex, struct hash *countTracks,
 	 *	char *chromName, unsigned chromSize)
 	 */
 
 
 static void countOneTdb(char *db, struct trackDb *tdb,
     struct hash *countTracks, char *chromName, unsigned chromSize,
       char *errorString)
 /* for this tdb in this db, count it up and provide a sample */
 {
 char *bigDataUrl = trackDbSetting(tdb, "bigDataUrl");
 boolean compositeContainer = tdbIsComposite(tdb);
 boolean compositeView = tdbIsCompositeView(tdb);
 boolean superChild = tdbIsSuperTrackChild(tdb);
 boolean depthSearch = cartUsualBoolean(cart, "depthSearch", FALSE);
 boolean protectedData = protectedTrack(tdb, tdb->track);
 hashCountTrack(tdb, countTracks);
 
 if (compositeContainer)
     hPrintf("    <li><b>%s</b>: %s : composite track container has %d subtracks</li>\n", tdb->track, tdb->type, slCount(tdb->subtracks));
 else if (compositeView)
     hPrintf("    <li><b>%s</b>: %s : composite view of parent: %s</li>\n", tdb->track, tdb->type, tdb->parent->track);
 else if (superChild)
     {
     if (isSupportedType(tdb->type))
         sampleUrl(NULL, db, tdb, errorString);
     else
 	hPrintf("    <li><b>%s</b>: %s : superTrack child of parent: %s</li>\n", tdb->track, tdb->type, tdb->parent->track);
     }
 else if (! depthSearch && bigDataUrl)
     {
     if (protectedData)
 	hPrintf("    <li><b>%s</b>: %s : &lt;protected data&gt;</li>\n", tdb->track, tdb->type);
     else
 	hPrintf("    <li><b>%s</b>: %s : %s</li>\n", tdb->track, tdb->type, bigDataUrl);
     }
 else
     {
     if (isSupportedType(tdb->type))
         sampleUrl(NULL, db, tdb, errorString);
     else
         hPrintf("    <li><b>%s</b>: %s</li>\n", tdb->track, tdb->type);
     }
 
 if (allTrackSettings)
     {
     hPrintf("    <li><ul>\n");
     trackSettings(tdb, countTracks); /* show all settings */
     hPrintf("    </ul></li>\n");
     }
 else if (tdb->subtracks)
     {
     showSubTracks(NULL, db, tdb, countTracks, chromName, chromSize, NULL);
     }
 return;
 }	/*	static void countOneTdb(char *db, struct trackDb *tdb,
 	 *	struct hash *countTracks, char *chromName,
 	 *	unsigned chromSize)
 	 */
 
 static unsigned largestChrom(char *db, char **nameReturn, int *chromCount)
 /* return the length and get the chrom name for the largest chrom
  * from chromInfo table.  For use is sample getData URLs
  */
 {
 char query[1024];
 struct sqlConnection *conn = hAllocConn(db);
 sqlSafef(query, sizeof(query), "select chrom,size from chromInfo order by size desc limit 1");
 struct sqlResult *sr = sqlGetResult(conn, query);
 char **row = sqlNextRow(sr);
 unsigned length = 0;
 if (row)
    {
    *nameReturn = cloneString(row[0]);
    length = sqlLongLong(row[1]);
    }
 sqlFreeResult(&sr);
 if (chromCount)
     {
     sqlSafef(query, sizeof(query), "select count(*) from chromInfo");
     *chromCount = sqlQuickNum(conn, query);
     }
 hFreeConn(&conn);
 return length;
 }
 
 static void hubTrackList(struct trackHub *hub, struct trackDb *topTrackDb, struct trackHubGenome *genome)
 /* process the track list in a hub to show all tracks */
 {
 hPrintf("    <li><ul>\n");
 if (topTrackDb)
     {
     struct hash *countTracks = hashNew(0);
     struct trackDb *tdb = NULL;
     for ( tdb = topTrackDb; tdb; tdb = tdb->next )
 	{
 	char *bigDataIndex = NULL;
 	char *relIdxUrl = trackDbSetting(topTrackDb, "bigDataIndex");
 	if (relIdxUrl != NULL)
 	    bigDataIndex = trackHubRelativeUrl(genome->trackDbFile, relIdxUrl);
         char *defaultGenome = NULL;
         if (isNotEmpty(genome->name))
 	    defaultGenome = genome->name;
         char *chromName = NULL;
         unsigned chromSize = 0;
 	int chromCount = 0;
         if (isEmpty(genome->twoBitPath))
             chromSize = largestChrom(defaultGenome, &chromName, &chromCount);
 	hubCountOneTdb(hub, defaultGenome, tdb, bigDataIndex, countTracks, chromName, chromSize, defaultGenome);
 	if (timeOutReached())
 	    break;
 	}	/*	for ( tdb = topTrackDb; tdb; tdb = tdb->next )	*/
     hPrintf("    <li>%d different track types</li>\n",countTracks->elCount - 1);
     /* add this single genome count to the overall multi-genome counts */
     if (countTracks->elCount)
 	{
         hPrintf("        <li><ul>\n");
 	struct hashEl *hel, *helList = hashElListHash(countTracks);
 	slSort(&helList, hashElCmpIntValDesc);
 	for (hel = helList; hel; hel = hel->next)
 	    {
 	    if (sameOk("track count", hel->name))
 		continue;
             int prevCount = ptToInt(hashFindVal(trackCounter, hel->name));
 	    if (differentStringNullOk("track count", hel->name))
 		totalTracks += ptToInt(hel->val);
 	    hashReplace(trackCounter, hel->name, intToPt(prevCount + ptToInt(hel->val)));
 	    if (isSupportedType(hel->name))
 		hPrintf("        <li>%d - %s - supported</li>\n", ptToInt(hel->val), hel->name);
 	    else
 		hPrintf("        <li>%d - %s - not supported</li>\n", ptToInt(hel->val), hel->name);
 	    }
         hPrintf("        </ul></li>\n");
 	}
     }
 else
     hPrintf("    <li>no trackTopDb ?</li>\n");
 
 hPrintf("    </ul></li>\n");
 }	/*	static struct trackDb *hubTrackList()	*/
 
 static void hubAssemblySettings(struct trackHub *hub, struct trackHubGenome *genome)
 /* display all the assembly 'settingsHash' */
 {
 struct trackDb *tdb = obtainTdb(genome, NULL);
 
 hPrintf("    <li><ul>\n");
 struct hashEl *hel;
 struct hashCookie hc = hashFirst(genome->settingsHash);
 while ((hel = hashNext(&hc)) != NULL)
     {
     if (sameWord("description", hel->name) ||
 	sameWord("defaultPos", hel->name) ||
 	sameWord("organism", hel->name) ||
 	sameWord("groups", hel->name) ||
 	sameWord("twoBitPath", hel->name) ||
 	sameWord("genome", hel->name)
 	)
 	continue;	// already output in header
     if (sameWord("trackDb", hel->name))	/* examine the trackDb structure */
 	{
 	hubTrackList(hub, tdb, genome);
         }
     else
 	hPrintf("    <li><b>%s</b>: %s</li>\n", hel->name, (char *)hel->val);
     if (timeOutReached())
 	break;
     }
 hPrintf("    </ul></li>\n");
 }
 
 static unsigned largestChromInfo(struct chromInfo *ci, char **chromName)
 /* find largest chrom in this chromInfo, return name and size */
 {
 unsigned size = 0;
 char *name = NULL;
 struct chromInfo *el;
 for (el = ci; el; el = el->next)
     {
     if (el->size > size)
 	{
 	size = el->size;
 	name = el->chrom;
 	}
     }
 if (chromName)
     *chromName = name;
 return size;
 }
 
 static void hubInfo(char *tag, char *val)
 /* print one list element with the given tag and value, show <empty>
  * if not value present
  */
 {
 if (isNotEmpty(val))
     hPrintf("<li><b>%s</b>: '%s'</li>\n", tag, val);
 else
     hPrintf("<li><b>%s</b>: &lt;empty&gt;</li>\n", tag);
 }
 
 static void genomeList(struct trackHub *hubTop)
 /* follow the pointers from the trackHub to trackHubGenome and around
  * in a circle from one to the other to find all hub resources
  */
 {
 long totalAssemblyCount = 0;
 struct trackHubGenome *genome = hubTop->genomeList;
 
 hPrintf("<h4>genome sequences (and tracks) present in this track hub (<a href='%s/list/hubGenomes?hubUrl=%s' target=_blank>JSON example list hub genomes)</a></h4>\n", urlPrefix, hubTop->url);
 
 if (NULL == genome)
     {
     hPrintf("<h4>odd error, can not find a genomeList ? at url: '%s'</h4>\n", hubTop->url);
     return;
     }
 
 hPrintf("<ul>\n");
 long lastTime = clock1000();
 for ( ; genome; genome = genome->next )
     {
     ++totalAssemblyCount;
     char urlReference[2048];
     if (isNotEmpty(genome->twoBitPath))
 	{
 	hPrintf("<li><b>Assembly genome</b> '%s' <b>twoBitPath</b>: '%s'</li>\n", genome->name, genome->twoBitPath);
 	char *chromName = NULL;
 	struct chromInfo *ci = trackHubAllChromInfo(genome->name);
         unsigned chromSize = largestChromInfo(ci, &chromName);
 	char sizeString[64];
 	sprintLongWithCommas(sizeString, chromSize);
 	hPrintf("<li><b>Sequence count</b> %d, <b>largest</b>: %s at %s bases</li>\n", slCount(ci), chromName, sizeString);
        safef(urlReference, sizeof(urlReference), " <a href='%s/getData/sequence?hubUrl=%s;genome=%s;chrom=%s;start=%u;end=%u' target=_blank>JSON example sequence output: %s:%u-%u</a>", urlPrefix, hubTop->url, genome->name, chromName, chromSize/4, (chromSize/4)+128, chromName, chromSize/4, (chromSize/4)+128);
         hPrintf("<li>%s</li>\n", urlReference);
 	}
     safef(urlReference, sizeof(urlReference), " <a href='%s/list/tracks?hubUrl=%s;genome=%s%s' target=_blank>JSON example list tracks output</a>", urlPrefix, hubTop->url, genome->name, trackLeavesOnly ? ";trackLeavesOnly=1" : "");
     hPrintf("<li>%s</li>\n", urlReference);
     hubInfo("organism", genome->organism);
     hubInfo("name", genome->name);
     hubInfo("description", genome->description);
     hubInfo("groups", genome->groups);
     hubInfo("defaultPos", genome->defaultPos);
     hubInfo("trackDbFile", genome->trackDbFile);
     hubAssemblySettings(hubTop, genome);
     if (measureTiming)
 	{
 	long thisTime = clock1000();
 	hPrintf("<li><em>processing time %s: %ld millis</em></li>\n", genome->name, thisTime - lastTime);
 	hPrintf("<hr>\n");
         }
     if (timeOutReached())
 	break;
     }
 if (trackCounter->elCount)
     {
     hPrintf("    <li>total genome assembly count: %ld</li>\n", totalAssemblyCount);
     hPrintf("    <li>%ld total tracks counted, %d different track types:</li>\n", totalTracks, trackCounter->elCount);
     hPrintf("    <li><ul>\n");
     struct hashEl *hel, *helList = hashElListHash(trackCounter);
     slSort(&helList, hashElCmpIntValDesc);
     for (hel = helList; hel; hel = hel->next)
 	{
 	hPrintf("    <li>%d - %s - total</li>\n", ptToInt(hel->val), hel->name);
 	}
     hPrintf("    </ul></li>\n");
     }
 hPrintf("</ul>\n");
 }	/*	static void genomeList (hubTop)	*/
 
 static char *urlFromShortLabel(char *shortLabel)
 /* this is not a fair way to get the URL since shortLabel's are not
  * necessarily unique.  This is temporary.  TBD: need to always use URL
  * and then get the shortLabel
  */
 {
 char hubUrl[1024];
 char query[1024];
 struct sqlConnection *conn = hConnectCentral();
 // Build a query to select the hubUrl for the given shortLabel
 sqlSafef(query, sizeof(query), "select hubUrl from %s where shortLabel='%s'",
       hubPublicTableName(), shortLabel);
 if (! sqlQuickQuery(conn, query, hubUrl, sizeof(hubUrl)))
     hubUrl[0] = 0;
 hDisconnectCentral(&conn);
 return cloneString(hubUrl);
 }
 
 static struct hash *apiFunctionHash = NULL;
 
 static void setupFunctionHash()
 /* initialize the apiFunctionHash */
 {
 if (apiFunctionHash)
     return;	/* already done */
 
 apiFunctionHash = hashNew(0);
 hashAdd(apiFunctionHash, "list", &apiList);
 hashAdd(apiFunctionHash, "getData", &apiGetData);
+hashAdd(apiFunctionHash, "search", &apiSearch);
 }
 
 static struct hashEl *parsePathInfo(char *pathInfo, char *words[MAX_PATH_INFO])
 /* given a pathInfo string: /command/subCommand/etc...
  *  parse that and return a function pointer and the parsed words
  * Returns NULL if not recognized
  */
 {
 char *tmp = cloneString(pathInfo);
 /* skip the first leading slash to simplify chopByChar parsing */
 tmp += 1;
 int wordCount = chopByChar(tmp, '/', words, MAX_PATH_INFO);
 if (wordCount < 1 || wordCount > 2)
     return NULL;	/* only 2 words allowed */
 
 struct hashEl *hel = hashLookup(apiFunctionHash, words[0]);
 return hel;
 }
 
 static void tracksForUcscDb(char *db)
 /* scan the specified database for all tracks */
 {
 struct hash *countTracks = hashNew(0);
 char *chromName = NULL;
 int chromCount = 0;
 unsigned chromSize = largestChrom(db, &chromName, &chromCount);
 char countString[64];
 sprintLongWithCommas(countString, chromCount);
 char sizeString[64];
 sprintLongWithCommas(sizeString, chromSize);
 hPrintf("<h4>Tracks in UCSC genome: '%s', chrom count: %s, longest chrom: %s : %s</h4>\n", db, countString, chromName, sizeString);
 
 char urlReference[2048];
 safef(urlReference, sizeof(urlReference), " <a href='%s/list/tracks?genome=%s%s' target=_blank>JSON output: list tracks</a>", urlPrefix, db, trackLeavesOnly ? ";trackLeavesOnly=1" : "");
 hPrintf("<h4>%s</h4>\n", urlReference);
 
 struct trackDb *tdbList = obtainTdb(NULL, db);
 struct trackDb *tdb;
 hPrintf("<ul>\n");
 for (tdb = tdbList; tdb != NULL; tdb = tdb->next )
     {
     countOneTdb(db, tdb, countTracks, chromName, chromSize, NULL);
     if (timeOutReached())
 	break;
     }
 int trackCount = ptToInt(hashFindVal(countTracks, "track count"));
 /* elCount - 1 since the 'track count' element isn't a track */
 hPrintf("    <li>%d total tracks counted, %d different track types</li>\n", trackCount, countTracks->elCount - 1);
 if (countTracks->elCount)
     {
     hPrintf("        <ul>\n");
     struct hashEl *hel, *helList = hashElListHash(countTracks);
     slSort(&helList, hashElCmpIntValDesc);
     for (hel = helList; hel; hel = hel->next)
 	{
 	if (sameOk("track count", hel->name))
 	    continue;
 	if (isSupportedType(hel->name))
 	    hPrintf("        <li>%d - %s - supported</li>\n", ptToInt(hel->val), hel->name);
 	else
 	    hPrintf("        <li>%d - %s - not supported</li>\n", ptToInt(hel->val), hel->name);
 	}
     hPrintf("        </ul>\n");
     }
 hPrintf("</ul>\n");
 hPrintf("</p>\n");
 }	// static void tracksForUcscDb(char * db)
 
 static void initUrlPrefix()
 /* set up urlPrefix for self referenes */
 {
 char *httpHost = getenv("HTTP_HOST");
 
 if (isEmpty(httpHost))
     urlPrefix = "";
 else
     {
     if (! startsWith("hgwdev-api", httpHost))
 	{
 	if (startsWith("hgwdev",httpHost) || startsWith("genome-test", httpHost))
 	    {
 	    urlPrefix = "../cgi-bin/hubApi";
 	    }
 	}
     }
 }
 
 static void showCartDump()
 /* for information purposes only during development, will become obsolete */
 {
 hPrintf("<h4>cart dump</h4>");
 hPrintf("<pre>\n");
 cartDump(cart);
 hPrintf("</pre>\n");
 }
 
 static void sendJsonHogMessage(char *hogHost)
 {
 apiErrAbort(err429, err429Msg, "Your host, %s, has been sending too many requests lately and is "
        "unfairly loading our site, impacting performance for other users. "
        "Please contact genome@soe.ucsc.edu to ask that your site "
        "be reenabled.  Also, please consider downloading sequence and/or "
        "annotations in bulk -- see http://genome.ucsc.edu/downloads.html.",
        hogHost);
 }
 
 static void sendHogMessage(char *hogHost)
 {
 puts("Content-Type:text/html");
 hPrintf("Status: %d %s\n", err429, err429Msg);
 puts("Retry-After: 30");
 puts("\n");
 
 hPrintf("<!DOCTYPE HTML>\n");
 hPrintf("<html lang='en'>\n");
 hPrintf("<head>\n");
 hPrintf("<meta charset=\"utf-8\">\n");
 hPrintf("<title>Status %d %s</title></head>\n", err429, err429Msg);
 
 hPrintf("<body><h1>Status %d %s</h1><p>\n", err429, err429Msg);
 hPrintf("Your host, %s, has been sending too many requests lately and is "
        "unfairly loading our site, impacting performance for other users. "
        "Please contact genome@soe.ucsc.edu to ask that your site "
        "be reenabled.  Also, please consider downloading sequence and/or "
        "annotations in bulk -- see http://genome.ucsc.edu/downloads.html.",
        hogHost);
 hPrintf("</p></body></html>\n");
 cgiExitTime("hubApi hogExit", enteredMainTime);
 exit(0);
 }
 
 static void hogExit()
 /* bottleneck server requests exit */
 {
 char *hogHost = getenv("REMOTE_ADDR");
 char *pathInfo = getenv("PATH_INFO");
 /* nothing on incoming path, then display the WEB page instead */
 if (sameOk("/",pathInfo))
     pathInfo = NULL;
 if (isNotEmpty(pathInfo))
     {
     sendJsonHogMessage(hogHost);
     }
 else
     {
     sendHogMessage(hogHost);
     }
 }	/*	static void hogExit()	*/
 
 /* name of radio button group */
 #define RADIO_GROUP	"selectRadio"
 /* button functions */
 #define RADIO_PUBHUB	"pubHub"
 #define RADIO_OTHERHUB	"otherHub"
 #define RADIO_UCSCDB	"ucscDb"
 
 static void selectionForm()
 /* setup the selection pull-downs for source */
 {
 char *hubDropDown = cartUsualString(cart, "publicHubs", defaultHub);
 char *urlDropDown = urlFromShortLabel(hubDropDown);
 char *otherHubUrl = cartUsualString(cart, "urlHub", "");
 char *ucscDb = cartUsualString(cart, "ucscGenome", defaultDb);
 
 if (isEmpty(otherHubUrl))
     otherHubUrl = urlDropDown;
 
 char *radioOn = cartUsualString(cart, RADIO_GROUP, RADIO_PUBHUB);
 
 /* create border around table, but not inside the table with the data */
 hPrintf("<table border=4>\n");
 hPrintf("<tr><td><table border=0>\n");
 hPrintf("<tr><th colspan=3>Select one of these three sources, and display options:</th></tr>\n");
 
 int maxDbNameWidth = 0;
 struct dbDb *dbList = ucscDbDb();
 char **ucscDbList = NULL;
 int listSize = slCount(dbList);
 AllocArray(ucscDbList, listSize);
 struct dbDb *el = dbList;
 int ucscDataBaseCount = 0;
 for ( ; el != NULL; el = el->next )
     {
     ucscDbList[ucscDataBaseCount++] = el->name;
     if (strlen(el->name) > maxDbNameWidth)
 	maxDbNameWidth = strlen(el->name);
     }
 maxDbNameWidth += 1;
 
 hPrintf("<form action='%s' name='hubApiUrl' id='hubApiUrl' method='GET'>\n\n", urlPrefix);
 
 hWrites("<tr><td>");
 jsMakeTrackingRadioButton(RADIO_GROUP, "typeOneJs", RADIO_PUBHUB, radioOn);
 hWrites("</td><th>");
 hWrites("Select public hub:");
 hWrites("</th><td>");
 
 #define JBUFSIZE 2048
 #define SMALLBUF 256
 char javascript[JBUFSIZE];
 struct slPair *events = NULL;
 safef(javascript, sizeof(javascript), "this.lastIndex=this.selectedIndex;");
 slPairAdd(&events, "focus", cloneString(javascript));
 
 cgiMakeDropListClassWithIdStyleAndJavascript("publicHubs", "publicHubs",
     shortLabels, publicHubCount, hubDropDown, NULL, "width: 400px", events);
 
 jsOnEventById("change", "publicHubs", "document.getElementById('"RADIO_GROUP"_"RADIO_PUBHUB"').checked=true;");
 hWrites("</td></tr>\n");
 
 hWrites("<tr><td>");
 jsMakeTrackingRadioButton(RADIO_GROUP, "typeOneJs", RADIO_OTHERHUB, radioOn);
 hWrites("</td><th>");
 hWrites("enter a hub URL:");
 hWrites("</th><td>");
 hPrintf("<input type='text' name='urlHub' id='urlHub' size='60' value='%s'>\n", otherHubUrl);
 jsOnEventById("change", "urlHub", "document.getElementById('"RADIO_GROUP"_"RADIO_OTHERHUB"').checked=true;");
 hWrites("</td></tr>\n");
 
 hWrites("<tr><td>");
 jsMakeTrackingRadioButton(RADIO_GROUP, "typeOneJs", RADIO_UCSCDB, radioOn);
 hWrites("</td><th>");
 hWrites("select a UCSC database name:");
 hWrites("</th><td>");
 maxDbNameWidth *= 9;  // 9 should be font width here
 char widthPx[SMALLBUF];
 safef(widthPx, sizeof(widthPx), "width: %dpx", maxDbNameWidth);
 cgiMakeDropListClassWithIdStyleAndJavascript("ucscGenome", "ucscGenome",
     ucscDbList, ucscDataBaseCount, ucscDb, NULL, widthPx, events);
 jsOnEventById("change", "ucscGenome", "document.getElementById('"RADIO_GROUP"_"RADIO_UCSCDB"').checked=true;");
 hWrites("</td></tr>\n");
 
 allTrackSettings = cartUsualBoolean(cart, "allTrackSettings", FALSE);
 hWrites("<tr><td>&nbsp;</td><th>display control:</th><td>");
 hCheckBox("allTrackSettings", allTrackSettings);
 hWrites("&nbsp;display all track settings for each track");
 hWrites("</td></tr>\n");
 
 trackLeavesOnly = cartUsualBoolean(cart, "trackLeavesOnly", trackLeavesOnly);
 hWrites("<tr><td>&nbsp;</td><th>JSON list output:</th><td>");
 hCheckBox("trackLeavesOnly", trackLeavesOnly);
 hWrites("&nbsp;show only data tracks, do not show composite container information");
 hWrites("</td></tr>\n");
 
 jsonOutputArrays = cartUsualBoolean(cart, "jsonOutputArrays", jsonOutputArrays);
 hWrites("<tr><td>&nbsp;</td><th>JSON output type:</th><td>");
 hCheckBox("jsonOutputArrays", jsonOutputArrays);
 hWrites("&nbsp;more array data than objects (default: mostly object output)");
 hWrites("</td></tr>\n");
 
 /* go button at the bottom of the table */
 hWrites("<tr><td>&nbsp;</td><td align=center>");
 hButton("sourceSelected", "go");
 hWrites("</td><td>press 'go' after selections made</td></tr>\n");
 
 hPrintf("</form>\n");
 
 hPrintf("<tr><th colspan=3>(example JSON list output: <a href='/list/publicHubs' target=_blank>Public hubs</a>, and <a href='/list/ucscGenomes' target=_blank>UCSC database genomes</a>)</th></tr>\n");
 
 hPrintf("</table>\n");
 hPrintf("</td></tr></table>\n");
 
 /* how does debug carry forward ? */
 // if (debug)
 //    cgiMakeHiddenVar("debug", "1");
 }
 
 static void apiRequest(char *pathInfo)
 {
 hPrintDisable();
 /*expect no more than MAX_PATH_INFO number of words*/
 char *words[MAX_PATH_INFO];
 /* can immediately verify valid parameters right here right now */
 char *start = cgiOptionalString("start");
 char *end = cgiOptionalString("end");
 char *db = cgiOptionalString("genome");
 char *hubUrl = cgiOptionalString("hubUrl");
 struct dyString *errorMsg = dyStringNew(128);
 
 if (isEmpty(hubUrl) && isNotEmpty(db))
     {
     struct sqlConnection *conn = hAllocConnMaybe(db);
     if (NULL == conn)
         dyStringPrintf(errorMsg, "can not find genome genome='%s' for endpoint '%s'", db, pathInfo);
     else
         hFreeConn(&conn);
     }
 if (isNotEmpty(start) || isNotEmpty(end))
     {
     long long llStart = -1;
     long long llEnd = -1;
     struct errCatch *errCatch = errCatchNew();
     if (errCatchStart(errCatch))
         {
         if (isNotEmpty(start))
             llStart = sqlLongLong(start);
         if (isNotEmpty(end))
             llEnd = sqlLongLong(end);
         }
     errCatchEnd(errCatch);
     if (errCatch->gotError)
         {
         if (isNotEmpty(errorMsg->string))
             dyStringPrintf(errorMsg, ", ");
         dyStringPrintf(errorMsg, "%s", errCatch->message->string);
         if (isNotEmpty(start) && (-1 == llStart))
             dyStringPrintf(errorMsg, ", can not recognize start coordinate: '%s'", start);
         if (isNotEmpty(end) && (-1 == llEnd))
             dyStringPrintf(errorMsg, ", can not recognize end coordinate: '%s'", end);
         }
     else
         {
         if ( (llStart < 0) || (llEnd < 0) || (llEnd <= llStart) )
             {
             if (isNotEmpty(errorMsg->string))
                 dyStringPrintf(errorMsg, ", ");
             dyStringPrintf(errorMsg, "illegal start,end coordinates given: %s,%s, 'end' must be greater than 'start', and start greater than or equal to zero", start, end);
             }
         }
     errCatchFree(&errCatch);
     }
 
 if (isNotEmpty(errorMsg->string))
     apiErrAbort(err400, err400Msg, "%s", errorMsg->string);
 
 setupFunctionHash();
 struct hashEl *hel = parsePathInfo(pathInfo, words);
 /* verify valid API command */
 if (hel)	/* have valid command */
     {
     hPrintDisable();
     void (*apiFunction)(char **) = hel->val;
     (*apiFunction)(words);
     return;
     }
  else
     apiErrAbort(err400, err400Msg, "no such command: '/%s", pathInfo);
     /* due to Apache rewrite rules, will never be called with this error */
 }	/*	static void apiRequest(char *pathInfo)	*/
 
 static void doMiddle(struct cart *theCart)
 /* Set up globals and make web page */
 {
 cart = theCart;
 // measureTiming = isNotEmpty(cartOptionalString(cart, "measureTiming"));
 char *database = NULL;
 char *genome = NULL;
 
 if (measureTiming)
     startProcessTiming();
 
 cgiVarSet("ignoreCookie", "1");
 
 getDbAndGenome(cart, &database, &genome, oldVars);
 initGenbankTableNames(database);
 initUrlPrefix();
 
 trackLeavesOnly = cartUsualBoolean(cart, "trackLeavesOnly", trackLeavesOnly);
 jsonOutputArrays = cartUsualBoolean(cart, "jsonOutputArrays", jsonOutputArrays);
 
 char *pathInfo = getenv("PATH_INFO");
 /* nothing on incoming path, then display the WEB page instead */
 if (sameOk("/",pathInfo))
     pathInfo = NULL;
 
 (void) hubPublicDbLoadAll();
 
 webStartJWest(cart, database, "Genome Browser API");
 // webStartGbNoBanner(cart, database, "UCSC JSON API interface");
 // webStartGbOptionalBanner(cart, database, "UCSC JSON API interface", TRUE, FALSE);
 
 hPrintf("<div class='container-fluid gbPage'>\n");
 /* these style mentions need to go into custom css file */
 hPrintf("<div style='border:10px solid white'>\n");
 
 if (debug)
     {
     hPrintf("<ul>\n");
     hPrintf("<li>hgBotDelay: %d</li>\n", botDelay);
     char *envVar = getenv("BROWSER_HOST");
     hPrintf("<li>BROWSER_HOST:%s</li>\n", envVar);
     envVar = getenv("CONTEXT_DOCUMENT_ROOT");
     hPrintf("<li>CONTEXT_DOCUMENT_ROOT:%s</li>\n", envVar);
     envVar = getenv("CONTEXT_PREFIX");
     hPrintf("<li>CONTEXT_PREFIX:%s</li>\n", envVar);
     envVar = getenv("DOCUMENT_ROOT");
     hPrintf("<li>DOCUMENT_ROOT:%s</li>\n", envVar);
     envVar = getenv("HTTP_HOST");
     hPrintf("<li>HTTP_HOST:%s</li>\n", envVar);
     envVar = getenv("REQUEST_URI");
     hPrintf("<li>REQUEST_URI:%s</li>\n", envVar);
     envVar = getenv("SCRIPT_FILENAME");
     hPrintf("<li>SCRIPT_FILENAME:%s</li>\n", envVar);
     envVar = getenv("SCRIPT_NAME");
     hPrintf("<li>SCRIPT_NAME:%s</li>\n", envVar);
     envVar = getenv("SCRIPT_URI");
     hPrintf("<li>SCRIPT_URI:%s</li>\n", envVar);
     envVar = getenv("SCRIPT_URL");
     hPrintf("<li>SCRIPT_URL:%s</li>\n", envVar);
     envVar = getenv("SERVER_NAME");
     hPrintf("<li>SERVER_NAME:%s</li>\n", envVar);
     envVar = getenv("PATH_INFO");
     if (isNotEmpty(envVar))
        hPrintf("<li>PATH_INFO:'%s'</li>\n", envVar);
     else
        hPrintf("<li>PATH_INFO:&lt;empty&gt;</li>\n");
     hPrintf("</ul>\n");
     }
 
 char *otherHubUrl = cartUsualString(cart, "urlHub", "");
 char *hubDropDown = cartUsualString(cart, "publicHubs", defaultHub);
 char *urlDropDown = urlFromShortLabel(hubDropDown);
 char *ucscDb = cartUsualString(cart, "ucscGenome", defaultDb);
 char *selectRadio = cartUsualString(cart, RADIO_GROUP, RADIO_PUBHUB);
 char *urlInput = urlDropDown;	/* assume public hub */
 if (debug)
     {
     hPrintf("<ul>\n");
     hPrintf("<li>otherHubUrl: '%s'</li>\n", otherHubUrl);
     hPrintf("<li>hubDropDown: '%s'</li>\n", hubDropDown);
     hPrintf("<li>urlDropDown: '%s'</li>\n", urlDropDown);
     hPrintf("<li>ucscDb: '%s'</li>\n", ucscDb);
     hPrintf("<li>urlInput: '%s'</li>\n", urlInput);
     hPrintf("</ul>\n");
     }
 if (isEmpty(otherHubUrl))
     otherHubUrl = urlInput;
 
 if (sameWord(RADIO_OTHERHUB, selectRadio))	/* requested other hub URL */
     urlInput = otherHubUrl;
 
 long lastTime = clock1000();
 struct trackHub *hub = errCatchTrackHubOpen(urlInput);
 if (measureTiming)
     {
     long thisTime = clock1000();
     hPrintf("<em>hub open time: %ld millis</em><br>\n", thisTime - lastTime);
     }
 
 hPrintf("<h3>Documentation: <a href='../../goldenPath/help/api.html'>API definitions/help</a>, and <a href='../../goldenPath/help/trackDb/trackDbHub.html' target=_blank>Track definition document</a> for definitions of track settings.</h3>\n");
 
 if (debug)
     showCartDump();
 
 hPrintf("<h2>Explore hub or database assemblies and tracks</h2>\n");
 
 selectionForm();
 
 /* these style mentions need to go into custom css file */
 hPrintf("<div style='height:500px;overflow:scroll'>\n");
 
 if (sameWord(RADIO_UCSCDB, selectRadio))  /* requested UCSC db track list */
     {
     tracksForUcscDb(ucscDb);
     }
 else
     {
     hPrintf("<h3>%s url: <em>%s</em></h3>\n", sameWord(RADIO_PUBHUB,selectRadio) ? "Public hub" : "Other hub", urlInput);
     hPrintf("<ul>\n");
     hubInfo("hub name", hub->name);
     hubInfo("short label", hub->shortLabel);
     hubInfo("long label", hub->longLabel);
     hubInfo("genomes file", hub->genomesFile);
     hubInfo("default db", hub->defaultDb);
     hubInfo("description url", hub->descriptionUrl);
     hubInfo("email", hub->email);
     if (debug)
 	{
 	hubInfo("version", hub->version);	/* UCSC internal info */
 	hubInfo("level", hub->level);		/* UCSC internal info */
 	}
     hPrintf("</ul>\n");
 
     genomeList(hub);
     }
 
 if (timedOut)
     hPrintf("<h1>Reached time out %ld seconds</h1>", timeOutSeconds);
 if (measureTiming)
     hPrintf("<em>Overall total time: %ld millis</em><br>\n", clock1000() - enteredMainTime);
 
 hPrintf("</div> <!-- end of text analysis output -->\n");
 hPrintf("</div> <!-- end of surrounding border-->\n");
 hPrintf("</div> <!-- end this page contents -->\n");
 
 webIncludeFile("inc/jWestFooter.html");
 webEndJWest();
 // cartWebEnd();
 }	/*	void doMiddle(struct cart *theCart)	*/
 
 static void setGlobalCgiVars()
 /* check for legal CGI variables and set global flags */
 {
 /* count the arguments to see if any occur more than once */
 struct hash *varCounter = hashNew(0);
 struct cgiVar *varList = cgiVarList();
 struct cgiVar *el = varList;
 for ( ; el; el = el->next)
     {
     hashIncInt(varCounter, el->name);
     }
 struct hashCookie cookie = hashFirst(varCounter);
 struct hashEl *hel = NULL;
 for ( hel = hashNext(&cookie); hel; hel = hashNext(&cookie))
     {
     if (ptToInt(hel->val) > 1)
 	apiErrAbort(err400, err400Msg, "parameter '%s' found %d times, only one instance allowed", hel->name, ptToInt(hel->val));
     }
 
 char *trackLeaves = cgiOptionalString("trackLeavesOnly");
 if (isNotEmpty(trackLeaves))
     {
     if (sameString("1", trackLeaves))
 	trackLeavesOnly = TRUE;
     else if (sameString("0", trackLeaves))
 	trackLeavesOnly = FALSE;
     else
 	apiErrAbort(err400, err400Msg, "unrecognized 'trackLeavesOnly=%s' argument, can only be =1 or =0", trackLeaves);
     }
 
 char *jsonArray = cgiOptionalString("jsonOutputArrays");
 if (isNotEmpty(jsonArray))
     {
     if (sameString("1", jsonArray))
 	jsonOutputArrays = TRUE;
     else if (sameString("0", jsonArray))
 	jsonOutputArrays = FALSE;
     else
 	apiErrAbort(err400, err400Msg, "unrecognized 'jsonOutputArrays=%s' argument, can only be =1 or =0", jsonArray);
     }
 
 int maybeDebug = cgiOptionalInt("debug", 0);
 if (1 == maybeDebug)
     debug = TRUE;
 
 char *measTime = cgiOptionalString("measureTiming");
 if (isNotEmpty(measTime) && sameWord("1", measTime))
     measureTiming = TRUE;
 char *maxOut = cgiOptionalString("maxItemsOutput");
 if (isNotEmpty(maxOut))
     {
     long long n = -2;
     struct errCatch *errCatch = errCatchNew();
     if (errCatchStart(errCatch))
         {
 	n = sqlLongLong(maxOut);
         }
     errCatchEnd(errCatch);
     if (errCatch->gotError)
 	apiErrAbort(err400, err400Msg, "can not recognize maxItemsOutput '%s' as a number", maxOut);
     else
 	{
 	if (n == -1)	/* can use -1 to indicate as much as allowed */
 	    maxItemsOutput = maxItemLimit;
 	else if (n > maxItemLimit)	/* safety check */
 	    apiErrAbort(err400, err400Msg, "requested maxItemsOutput '%s' greater than maximum limit allowed: %d", maxOut, maxItemLimit);
 	else if (n < 1)
 	    apiErrAbort(err400, err400Msg, "requested maxItemsOutput '%s' can not be less than one", maxOut, maxItemLimit);
 	else
 	    maxItemsOutput = n;
 	}
     }
 }	/*	static void setGlobalCgiVars()	*/
 
 static void redirectToHelp()
 /* redirect to the help page */
 {
 puts("Content-Type:text/html");
 hPrintf("Status: %d %s\n", err301, err301Msg);
 hPrintf("Location: /goldenPath/help/api.html\n");
 puts("\n");
 
 hPrintf("<!DOCTYPE HTML>\n");
 hPrintf("<html lang='en'>\n");
 hPrintf("<head>\n");
 hPrintf("<meta http-equiv='Refresh' content='0; url=/goldenPath/help/api.html' />\n");
 hPrintf("</head>\n");
 }
 
 /* Null terminated list of CGI Variables we don't want to save
  * permanently. */
 static char *excludeVars[] = {"Submit", "submit", "sourceSelected", "selectRadio", "ucscGenome", "publicHubs", "clade", NULL,};
 
 int main(int argc, char *argv[])
 /* Process command line. */
 {
 enteredMainTime = clock1000();
 cgiSpoof(&argc, argv);
 verboseTimeInit();
 /* similar delay system as in DAS server */
 botDelay = hgBotDelayTimeFrac(delayFraction);
 if (botDelay > 0)
     {
     if (botDelay > 2000)
         {
 	sleep1000(botDelay);
 	hogExit();
         return 0;
         }
     sleep1000(botDelay);
     }
 
 setGlobalCgiVars();
 
 #ifdef NOTUSED // the argument processing doesn't allow udcTimeout
 int timeout = cgiOptionalInt("udcTimeout", 300);
 if (udcCacheTimeout() < timeout)
     udcSetCacheTimeout(timeout);
 #endif
 setUdcCacheDir();
 
 initSupportedTypes();
 
 char *pathInfo = getenv("PATH_INFO");
 if (isNotEmpty(pathInfo)) /* can get to this immediately, no cart needed */
     apiRequest(pathInfo);
 else
     {
     char *allowApiHtml = cfgOptionDefault("hubApi.allowHtml", "off");
     if (sameWord("on", allowApiHtml))
 	{
 	trackCounter = hashNew(0);
 	cartEmptyShellNoContent(doMiddle, hUserCookie(), excludeVars, oldVars);
 	}
     else
 	redirectToHelp();
     }
 cgiExitTime("hubApi", enteredMainTime);
 return 0;
 }