48c5bcc0735d09367f9853ebbd053aea3f94b262
chmalee
  Fri May 15 10:37:48 2020 -0700
Fix bug in hub searching where a hub being down at the time we are trying to display it's search results caused a segfault, also removing remnants of hgHubConnect needing a special udcCache, which the CGI has not been using for months now, refs #25574

diff --git src/hg/hgHubConnect/hgHubConnect.c src/hg/hgHubConnect/hgHubConnect.c
index e588f73..0afb3c4 100644
--- src/hg/hgHubConnect/hgHubConnect.c
+++ src/hg/hgHubConnect/hgHubConnect.c
@@ -1,1863 +1,1859 @@
 /* hgHubConnect - the CGI web-based program to select track data hubs to connect with. */
 
 /* Copyright (C) 2014 The Regents of the University of California 
  * See README in this or parent directory for licensing information. */
 
 #include "common.h"
 #include "hash.h"
 #include "linefile.h"
 #include "errAbort.h"
 #include "errCatch.h"
 #include "hCommon.h"
 #include "dystring.h"
 #include "jksql.h"
 #include "cheapcgi.h"
 #include "htmshell.h"
 #include "hdb.h"
 #include "hui.h"
 #include "cart.h"
 #include "dbDb.h"
 #include "web.h"
 #include "trackHub.h"
 #include "hubConnect.h"
 #include "dystring.h"
 #include "hPrint.h"
 #include "jsHelper.h"
 #include "obscure.h"
 #include "hgConfig.h"
 #include "trix.h"
 #include "net.h"
 #include "hubSearchText.h"
 
 struct cart *cart;	/* The user's ui state. */
 struct hash *oldVars = NULL;
 
 static char *pageTitle = "Track Data Hubs";
 char *database = NULL;
 char *organism = NULL;
 
 struct hubOutputStructure
     {
     struct hubOutputStructure *next;
     struct dyString *metaTags;
     struct dyString *descriptionMatch;
     struct genomeOutputStructure *genomes;
     int genomeCount;
     struct hash *genomeOutHash;
     };
 
 struct genomeOutputStructure
     {
     struct genomeOutputStructure *next;
     struct dyString *shortLabel;
     struct dyString *metaTags;
     struct dyString *descriptionMatch;
     struct tdbOutputStructure *tracks;
     struct dyString *assemblyLink;
     char *genomeName;
     char *positionString;
     int trackCount;
     struct hash *tdbOutHash;
     int hitCount;
     };
 
 struct tdbOutputStructure
     {
     struct tdbOutputStructure *next;
     struct dyString *shortLabel;
     struct dyString *metaTags;
     struct dyString *descriptionMatch;
     struct dyString *configUrl;
     struct tdbOutputStructure *children;
     int childCount;
     };
 
 struct hubEntry
 // for entries pulled from hubPublic
     {
     struct hubEntry *next;
     char *hubUrl;
     char *shortLabel;
     char *longLabel;
     char *dbList;
     char *errorMessage;
     int id;
     char *descriptionUrl;
     bool tableHasDescriptionField;
     };
 
 struct hubEntry *hubEntryTextLoad(char **row, bool hasDescription)
 {
 struct hubEntry *ret;
 AllocVar(ret);
 ret->hubUrl = cloneString(row[0]);
 ret->shortLabel = cloneString(row[1]);
 ret->longLabel = cloneString(row[2]);
 ret->dbList = cloneString(row[3]);
 ret->errorMessage = cloneString(row[4]);
 ret->id = sqlUnsigned(row[5]);
 if (hasDescription)
     ret->descriptionUrl = cloneString(row[6]);
 else
     ret->descriptionUrl = NULL;
 return ret;
 }
 
 
 static void ourCellStart()
 {
 fputs("<TD>", stdout);  // do not add a newline
 }
 
 static void ourCellEnd()
 {
 puts("</TD>");
 }
 
 static void ourPrintCellLink(char *str, char *url)
 {
 ourCellStart();
 printf("<A class=\"cv\" HREF=\"%s\" TARGET=_BLANK>\n", url);
 if (str != NULL)
     fputs(str, stdout); // do not add a newline -- was causing trailing blanks get copied in cut and paste 
 puts("</A>");
 ourCellEnd();
 }
 
 static void ourPrintCell(char *str)
 {
 ourCellStart();
 if (str != NULL)
     fputs(str, stdout); // do not add a newline -- was causing trailing blanks get copied in cut and paste 
 ourCellEnd();
 }
 
 static char *removeLastComma(char *string)
 {
 if (string != NULL)
     {
     int len = strlen(string);
 
     if ( string[len - 1] == ',')
 	string[len - 1]  = 0;
     else if (len > 2 && endsWith(string,", "))
         string[len - 2] = 0;
     }
 return string;
 }
 
 #define GENLISTWIDTH 40
 static void printGenomeList(char *hubUrl, struct slName *genomes, int row, boolean withLink)
 /* print supported assembly names from sl list */
 {
 struct dyString *dyHtml = newDyString(1024);
 struct dyString *dyShortHtml = newDyString(1024);
 
 // create two strings: one shortened to GENLISTWIDTH characters
 // and another one with all genomes
 int charCount = 0;
 struct slName *genome = genomes;
 for(; genome; genome = genome->next)
     {
     char *trimmedName = trackHubSkipHubName(genome->name);
     char *shortName = cloneString(trimmedName);
     // If even the first element is too long, truncate its short name.
     if (genome==genomes && strlen(trimmedName) > GENLISTWIDTH)  
         shortName[GENLISTWIDTH] = 0;
 
     // append to dyShortHtml if necessary
     if (charCount == 0 || (charCount+strlen(trimmedName)<=GENLISTWIDTH))
         { 
         if (withLink)
             dyStringPrintf(dyShortHtml,"<a title='Connect hub and open the %s assembly' href='hgTracks?hubUrl=%s&genome=%s&position=lastDbPos'>%s</a>" , genome->name, hubUrl, genome->name, shortName);
         else
             dyStringPrintf(dyShortHtml,"%s" , shortName);
         dyStringPrintf(dyShortHtml,", ");
         }
     freeMem(shortName); 
 
     charCount += strlen(trimmedName);
 
     // always append to dyHtml
     if (withLink)
         dyStringPrintf(dyHtml,"<a title='Connect hub and open the %s assembly' href='hgTracks?hubUrl=%s&genome=%s&position=lastDbPos'>%s</a>" , genome->name, hubUrl, genome->name, trimmedName);
     else
         dyStringPrintf(dyHtml,"%s" , trimmedName);
 
     if (genome->next)
         {
         dyStringPrintf(dyHtml,", ");
         }
 
     }
 
 char *longHtml = dyStringCannibalize(&dyHtml);
 char *shortHtml = dyStringCannibalize(&dyShortHtml);
 shortHtml = removeLastComma(shortHtml);
 
 if (charCount < GENLISTWIDTH)
     ourPrintCell(shortHtml);
 else
     {
     char id[256];
     char tempHtml[1024+strlen(longHtml)+strlen(shortHtml)];
     safef(tempHtml, sizeof tempHtml, 
 	"<span id=Short%d><span style='cursor:default' id='Short%dPlus'>[+]&nbsp;</span>%s...</span>"
 	"<span id=Full%d style=\"display:none\"><span style='cursor:default' id='Full%dMinus'>[-]<br></span>%s</span>"
 	, row, row, shortHtml
 	, row, row, longHtml);
 
     safef(id, sizeof id, "Short%dPlus", row);
     jsOnEventByIdF("click", id,
 	"document.getElementById('Short%d').style.display='none';"
 	"document.getElementById('Full%d').style.display='inline';"
 	"return false;"
 	, row, row);
 
     safef(id, sizeof id, "Full%dMinus", row);
     jsOnEventByIdF("click", id, 
 	"document.getElementById('Full%d').style.display='none';"
 	"document.getElementById('Short%d').style.display='inline';"
 	"return false;"
 	, row, row);
     ourPrintCell(tempHtml);
     }
 
 freeMem(longHtml);
 freeMem(shortHtml);
 }
 
 
 static void printGenomes(struct trackHub *thub, int row, boolean withLink)
 /* print supported assembly names from trackHub */
 {
 /* List of associated genomes. */
 struct trackHubGenome *genomes = thub->genomeList;
 struct slName *list = NULL, *el;
 for(; genomes; genomes = genomes->next)
     {
     el = slNameNew(genomes->name);
     slAddHead(&list, el);
     }
 slReverse(&list);
 printGenomeList(thub->url, list, row, withLink);
 }
 
 
 static void hgHubConnectUnlisted(struct hubConnectStatus *hubList, 
     struct hash *publicHash)
 /* Put up the list of unlisted hubs and other controls for the page. */
 /* uses publicHash to distingusih public hubs from unlisted ones */
 /* NOTE: Destroys hubList */
 {
 // put out the top of our page
 printf("<div id=\"unlistedHubs\" class=\"hubList\"> \n"
     "<table id=\"unlistedHubsTable\"> \n"
     "<thead><tr> \n"
 	"<th colspan=\"6\" id=\"addHubBar\"><label for=\"hubUrl\">URL:</label> \n"
 	"<input name=\"hubText\" id=\"hubUrl\" class=\"hubField\" "
 	    "type=\"text\" size=\"65\"> \n"
 	"<input name=\"hubAddButton\" id='hubAddButton' "
 		"class=\"hubField\" type=\"button\" value=\"Add Hub\">\n"
 	"</th> \n"
     "</tr> \n");
 jsOnEventById("click", "hubAddButton", 
     "var hubText = document.getElementById('hubUrl');"
     "hubText.value=$.trim(hubText.value);"
     "if(validateUrl($('#hubUrl').val())) { "
     " document.addHubForm.elements['hubUrl'].value=hubText.value;"
     " document.addHubForm.submit(); return true; } "
     "else { return false; }"
     );
 
 // count up the number of unlisted hubs we currently have
 int unlistedHubCount = 0;
 struct hubConnectStatus *unlistedHubList = NULL;
 struct hubConnectStatus *hub, *nextHub;
 
 for(hub = hubList; hub; hub = nextHub)
     {
     nextHub = hub->next;
     // if url is not in publicHash, it's unlisted */
     if (!((publicHash != NULL) && hashLookup(publicHash, hub->hubUrl)))
 	{
 	unlistedHubCount++;
 	slAddHead(&unlistedHubList, hub);
 	}
     }
 
 hubList = NULL;  // hubList no longer valid
 
 if (unlistedHubCount == 0)
     {
     // nothing to see here
     printf("<tr><td>No Unlisted Track Hubs</td></tr>");
     printf("</thead></table></div>");
     return;
     }
 
 // time to output the big table.  First the header
 printf(
     "<tr> "
 	"<th>Display</th> "
 	"<th>Hub Name</th> "
 	"<th>Description</th> "
 	"<th>Assemblies</th> "
     "</tr>\n"
     "</thead>\n");
 
 // start first row
 printf("<tbody>");
 
 char id[256];
 int count = 0;
 for(hub = unlistedHubList; hub; hub = hub->next)
     {
     char hubName[64];
     safef(hubName, sizeof(hubName), "%s%u", hgHubConnectHubVarPrefix, hub->id);
     if (!cartUsualBoolean(cart, hubName, FALSE))
 	continue;
 
     if (count)
 	webPrintLinkTableNewRow();  // ends last row and starts a new one
     count++;
 
     puts("<tr>");
 
     ourCellStart();
     safef(id, sizeof id, "hubDisconnectButtonU%d", count);
     printf("<input name=\"hubDisconnectButton\" id='%s' "
 	"class=\"hubDisconnectButton\" type=\"button\" value=\"Disconnect\">\n", id);
     jsOnEventByIdF("click", id, 
 	"document.disconnectHubForm.elements['hubId'].value='%d';"
 	"document.disconnectHubForm.submit(); return true;", hub->id);
     ourCellEnd();
 
     if (hub->trackHub != NULL)
 	{
 	ourPrintCellLink(hub->trackHub->shortLabel, hub->hubUrl);
 	}
     else
 	ourPrintCell("");
 
     boolean hubHasError = (!isEmpty(hub->errorMessage));
     if (hubHasError)
 	{
 	ourCellStart();
 	printf("<span class=\"hubError\">ERROR: %s </span>"
 	    "<a TARGET=_BLANK href=\"../goldenPath/help/hgTrackHubHelp.html#Debug\">Debug Help</a>\n", 
 	    hub->errorMessage);
 	
 	safef(id, sizeof id, "hubClearButton%d", count);
 	// give people a chance to clear the error 
 	printf("<input name=\"hubClearButton\"  id='%s' "
 		"class=\"hubButton\" type=\"button\" value=\"Retry Hub\">"
 		, id);
 	jsOnEventByIdF("click", id,
 	    "document.resetHubForm.elements['hubCheckUrl'].value='%s';"
 	    "document.resetHubForm.submit(); return true;", hub->hubUrl);
 	ourCellEnd();
 	}
     else if (hub->trackHub != NULL)
 	{
 	if (hub->trackHub->descriptionUrl != NULL)
 	    ourPrintCellLink(hub->trackHub->longLabel, hub->trackHub->descriptionUrl);
 	else
 	    ourPrintCell(hub->trackHub->longLabel);
 	}
     else
 	ourPrintCell("");
 
 
     if (hub->trackHub != NULL)
 	printGenomes(hub->trackHub, count, !hubHasError);
     else
 	ourPrintCell("");
 
     puts("</tr>");
     }
 
 printf("</tbody></TABLE>\n");
 printf("</div>");
 }
 
 void doValidateNewHub(char *hubUrl)
 /* Run hubCheck on a hub. */
 {
 struct dyString *cmd = dyStringNew(0);
 udcSetCacheTimeout(1);
 dyStringPrintf(cmd, "loader/hubCheck -htmlOut -noTracks %s", hubUrl);
 printf("<div id=\"validateHubResult\" class=\"hubTdbTree\" style=\"overflow: auto\"></div>");
 FILE *f = popen(cmd->string, "r");
 if (f == NULL)
     errAbort("popen: error running command: \"%s\"", cmd->string);
 char buf[1024];
 while (fgets(buf, sizeof(buf), f))
     {
     jsInlineF("%s", buf);
     }
 if (pclose(f) == -1)
     errAbort("pclose: error for command \"%s\"", cmd->string);
 // the 'false' below prevents a few hub-search specific jstree configuration options
 jsInline("hubSearchTree.init(false);");
 dyStringFree(&cmd);
 }
 
 void hgHubConnectDeveloperMode()
 /* Put up the controls for the "Hub Development" Tab, which includes a button to run the
  * hubCheck utility on a hub and load a hub with the udcTimeout and measureTiming
  * variables turned on */
 {
 // put out the top of our page
 char *hubUrl = cartOptionalString(cart, "validateHubUrl");
 boolean doHubValidate = cartVarExists(cart, hgHubDoHubCheck);
 
 // the outer div for all the elements in the tab
 printf("\n<div id=\"hubDeveloper\" class=\"hubList\">\n");
 
 // the row to enter in the url and the button and settings to validate/load
 printf("<div class=\"addHubBar roundCorners\" id=\"validateHubUrlRow\">\n");
 printf("<label for=\"validateHubUrl\"><b>URL:</b></label>\n");
 if (hubUrl != NULL)
     {
     printf("<input name=\"validateText\" id=\"validateHubUrl\" class=\"hubField\" "
         "type=\"text\" size=\"65\" value=\"%s\"> \n", hubUrl);
     }
 else
     {
     printf("<input name=\"validateText\" id=\"validateHubUrl\" class=\"hubField\" "
         "type=\"text\" size=\"65\"> \n");
     }
 printf("<input name=\"hubValidateButton\" id='hubValidateButton' "
     "class=\"hubField\" type=\"button\" value=\"Check Hub settings\">\n");
 printf(" or \n");
 printf("<input name=\"hubLoadMaybeTiming\" id='hubLoadMaybeTiming' "
     "class=\"hubField\" type=\"button\" value=\"View Hub on Genome Browser\">\n");
 printf("</div>\n"); // validateHubUrlRow div
 
 printf("<div id=\"extraSettingsContainer\" class=\"addHubBar\">\n");
 printf("<img id=\"advancedSettingsButton\" src=\"../images/add_sm.gif\">\n");
 printf("Optional View Hub Settings");
 
 char *measureTiming = cartCgiUsualString(cart, "measureTiming", NULL);
 char *udcTimeout = cartCgiUsualString(cart, "udcTimeout", NULL);
 boolean doMeasureTiming = isNotEmpty(measureTiming);
 boolean doUdcTimeout = isNotEmpty(udcTimeout);
 
 printf("<div id=\"extraSettingsList\" style=\"display: none\">\n");
 printf("<ul style=\"list-style-type:none\">\n<li>\n");
 // measureTiming first
 printf("<input name=\"addMeasureTiming\" id=\"addMeasureTiming\" "
     "class=\"hubField\" type=\"checkbox\" %s>", doMeasureTiming ? "checked": "");
 printf("<label for=\"addMeasureTiming\">Display load times</label>\n");
 
 // and a tooltip explaining this checkbox
 printf("<div class=\"tooltip\"> (?)\n");
 printf("<span class=\"tooltiptext\">"
     "Checking this box shows the timing measurements below the Genome Browser image. "
     "Useful for determining slowdowns to loading or drawing tracks."
     "</span>\n");
 printf("</div></li>\n"); // tooltip div
 
 printf("<li>\n");
 // udcTimeout enable/disable
 printf("<input name=\"disableUdcTimeout\" id=\"disableUdcTimeout\" "
     "class=\"hubField\" type=\"checkbox\" %s >", doUdcTimeout ? "checked" : "");
 printf("<label for=\"disableUdcTimeout\">Enable hub refresh</label>\n");
 // add a tooltip explaining these checkboxes
 printf("<div class=\"tooltip\"> (?)\n");
 printf("<span class=\"tooltiptext\">"
     "Checking this box changes the cache expiration time (default of 5 minutes) "
     "and allows the Genome Browser to reload Hub configuration and data files with each refresh."
     "</span>\n");
 printf("</div></li>\n"); // tooltip div
 printf("</ul>\n");
 printf("</div>\n"); // extraSettingsList div
 printf("</div>\n"); // extraSettingsContainer div
 
 if (hubUrl != NULL && doHubValidate)
     doValidateNewHub(hubUrl);
 else
     printf("<div id=\"hubDeveloperInstructions\">Enter URL to hub to check configuration settings "
         "or load hub </div> \n");
 printf("</div>"); // hubDeveloper div
 
 jsOnEventById("click", "hubValidateButton",
     "var validateText = document.getElementById('validateHubUrl');"
     "var udcTimeout = document.getElementById('disableUdcTimeout').checked === true;"
     "var doMeasureTiming = document.getElementById('addMeasureTiming').checked === true;"
     "validateText.value=$.trim(validateText.value);"
     "if(validateUrl($('#validateHubUrl').val())) { "
     " document.validateHubForm.elements['validateHubUrl'].value=validateText.value;"
     " document.validateHubForm.elements['" hgHubDoHubCheck "'].value='on';"
     " if (doMeasureTiming) { document.validateHubForm.elements['measureTiming'].value='1'; }"
     " else { document.validateHubForm.elements['measureTiming'].value=''}"
     " if (udcTimeout) { document.validateHubForm.elements['udcTimeout'].value='1'; }"
     " else { document.validateHubForm.elements['udcTimeout'].value=''}"
     " document.validateHubForm.submit(); return true; }"
     "else { return false; }"
     );
 jsOnEventById("click", "hubLoadMaybeTiming",
     "var validateText = document.getElementById('validateHubUrl');"
     "var udcTimeout = document.getElementById('disableUdcTimeout').checked === true;"
     "var doMeasureTiming = document.getElementById('addMeasureTiming').checked === true;"
     "validateText.value=$.trim(validateText.value);"
     "if(validateUrl($('#validateHubUrl').val())) {"
     "   loc = \"../cgi-bin/hgTracks?hgHub_do_firstDb=on&hgHub_do_redirect=on&hgHubConnect.remakeTrackHub=on\" + \"&hubUrl=\" + validateText.value;"
     "   if (doMeasureTiming) { loc += \"&measureTiming=1\";} else { loc += \"&measureTiming=[]\"; }"
     "   if (udcTimeout) { loc += \"&udcTimeout=5\"; } else { loc += \"&udcTimeout=[]\"; }"
     "   window.location.href=loc; "
     "} else { return false; }"
     );
 }
 
 static void addPublicHubsToHubStatus(struct sqlConnection *conn, char *publicTable, char  *statusTable)
 /* Add urls in the hubPublic table to the hubStatus table if they aren't there already */
 {
 char query[1024];
 sqlSafef(query, sizeof(query),
         "select %s.hubUrl from %s left join %s on %s.hubUrl = %s.hubUrl where %s.hubUrl is NULL",
         publicTable, publicTable, statusTable, publicTable, statusTable, statusTable); 
 struct sqlResult *sr = sqlGetResult(conn, query);
 char **row;
 while ((row = sqlNextRow(sr)) != NULL)
     {
     char *errorMessage = NULL;
     char *url = row[0];
 
     // add this url to the hubStatus table
     hubFindOrAddUrlInStatusTable(database, cart, url, &errorMessage);
     }
 }
 
 
 char *modifyTermsForHubSearch(char *hubSearchTerms, bool isStrictSearch)
 /* This won't exactly be pretty.  MySQL treats any sequence of alphanumerics and underscores
  * as a word, and single apostrophes are allowed as long as they don't come back-to-back.
  * Cut down to those characters, then add initial + (for requiring word) and * (for word
  * expansion) as appropriate. */
 {
 char *cloneTerms = cloneString(hubSearchTerms);
 struct dyString *modifiedTerms = dyStringNew(0);
 if (isNotEmpty(cloneTerms))
     {
     int i;
     for (i=0; i<strlen(cloneTerms); i++)
         {
         // allowed punctuation is underscore and apostrophe, and we'll do special handling for hyphen
         if (!isalnum(cloneTerms[i]) && cloneTerms[i] != '_' && cloneTerms[i] != '\'' &&
                 cloneTerms[i] != '-')
             cloneTerms[i] = ' ';
         }
     char *splitTerms[1024];
     int termCount = chopByWhite(cloneTerms, splitTerms, sizeof(splitTerms));
     for (i=0; i<termCount; i++)
         {
         char *hyphenatedTerms[1024];
         int hyphenTerms = chopString(splitTerms[i], "-", hyphenatedTerms, sizeof(hyphenatedTerms));
         int j;
         for (j=0; j<hyphenTerms-1; j++)
             {
             dyStringPrintf(modifiedTerms, "+%s ", hyphenatedTerms[j]);
             }
         if (isStrictSearch)
             dyStringPrintf(modifiedTerms, "+%s ", hyphenatedTerms[j]);
         else
             {
             dyStringPrintf(modifiedTerms, "+%s* ", hyphenatedTerms[j]);
             }
         }
     }
 return dyStringCannibalize(&modifiedTerms);
 }
 
 
 struct hubSearchText *getHubSearchResults(struct sqlConnection *conn, char *hubSearchTableName,
         char *hubSearchTerms, bool checkLongText, char *dbFilter, struct hash *hubLookup)
 /* Find hubs, genomes, and tracks that match the provided search terms.
  * Return all hits that satisfy the (optional) supplied assembly filter.
  * if checkLongText is FALSE, skip searching within the long description text entries */
 {
 char *cleanSearchTerms = cloneString(hubSearchTerms);
 if (isNotEmpty(cleanSearchTerms))
     tolowers(cleanSearchTerms);
 bool isStrictSearch = FALSE;
 char *modifiedSearchTerms = modifyTermsForHubSearch(cleanSearchTerms, isStrictSearch);
 struct hubSearchText *hubSearchResultsList = NULL;
 struct dyString *query = dyStringNew(100);
 char *noLongText = NULL;
 
 if (!checkLongText)
     noLongText = cloneString("textLength = 'Short' and");
 else
     noLongText = cloneString("");
 
 sqlDyStringPrintf(query, "select * from %s where %s match(text) against ('%s' in boolean mode)"
         " order by match(text) against ('%s' in boolean mode)",
         hubSearchTableName, noLongText, modifiedSearchTerms, modifiedSearchTerms);
 
 struct sqlResult *sr = sqlGetResult(conn, dyStringContents(query));
 char **row;
 while ((row = sqlNextRow(sr)) != NULL)
     {
     struct hubSearchText *hst = hubSearchTextLoadWithNullGiveContext(row, cleanSearchTerms);
     // Skip rows where the long text matched the more lax MySQL search (punctuation just
     // splits terms into two words, so "rna-seq" finds "rna" and "seq" separately, but
     // not the more strict rules used to find context for the search terms.
     if ((hst->textLength == hubSearchTextLong) && isEmpty(hst->text))
         continue;
     char *hubUrl = hst->hubUrl;
     struct hubEntry *hubInfo = hashFindVal(hubLookup, hubUrl);
     if (hubInfo == NULL)
         continue; // Search table evidently includes a hub that's not on this server.  Skip it.
     char *db = cloneString(hst->db);
     tolowers(db);
     if (isNotEmpty(dbFilter))
         {
         if (isNotEmpty(db))
             {
             if (stringIn(dbFilter, db) == NULL)
                 continue;
             }
         else
             {
             // no db in the hubSearchText means this is a top-level hub hit.
             // filter by the db list associated with the hub instead
             char *dbList = cloneString(hubInfo->dbList);
             tolowers(dbList);
             if (stringIn(dbFilter, dbList) == NULL)
                 continue;
             }
         }
     // Add hst to the list to be returned
     slAddHead(&hubSearchResultsList, hst);
     }
 slReverse(&hubSearchResultsList);
 return hubSearchResultsList;
 }
 
 
 void printSearchAndFilterBoxes(int searchEnabled, char *hubSearchTerms, char *dbFilter)
 /* Create the text boxes for search and database filtering along with the required
  * javscript */
 {
 char event[4096];
 if (searchEnabled)
     {
     safef(event, sizeof(event), 
             "document.searchHubForm.elements['hubSearchTerms'].value=$('#hubSearchTerms').val();"
             "document.searchHubForm.elements['hubDbFilter'].value=$('#hubDbFilter').val();"
             "document.searchHubForm.submit();return true;");
     printf("Enter search terms to find in public track hub description pages:<BR>"
             "<input name=\"hubSearchTerms\" id=\"hubSearchTerms\" class=\"hubField\" "
             "type=\"text\" size=\"65\" value=\"%s\"> \n",
             hubSearchTerms!=NULL?hubSearchTerms:"");
     printf("<br>\n");
     }
 else
     {
     safef(event, sizeof(event), 
             "document.searchHubForm.elements['hubDbFilter'].value=$('#hubDbFilter').val();"
             "document.searchHubForm.submit();return true;");
     }
 
 printf("Filter hubs by assembly: "
         "<input name=\"%s\" id=\"hubDbFilter\" class=\"hubField\" "
         "type=\"text\" size=\"10\" value=\"%s\"> \n"
         "<input name=\"hubSearchButton\" id='hubSearchButton' "
         "class=\"hubField\" type=\"button\" value=\"Search Public Hubs\">\n",
         hgHubDbFilter, dbFilter!=NULL?dbFilter:"");
 jsOnEventById("click", "hubSearchButton", event);
 puts("<br><br>\n");
 }
 
 
 void printSearchTerms(char *hubSearchTerms)
 /* Write out a reminder about the current search terms and a note about
  * how to navigate detailed search results */
 {
 printf("Displayed list <B>restricted by search terms:</B> %s\n", hubSearchTerms);
 puts("<input name=\"hubDeleteSearchButton\" id='hubDeleteSearchButton' "
         "class=\"hubField\" type=\"button\" value=\"Show All Hubs\">\n");
 
 jsOnEventById("click", "hubDeleteSearchButton",
         "document.searchHubForm.elements['hubSearchTerms'].value='';"
         "document.searchHubForm.elements['hubDbFilter'].value='';"
         "document.searchHubForm.submit();return true;");
 puts("<BR><BR>\n");
 printf("When exploring the detailed search results for a hub, you may right-click "
         "on an assembly or track line to open it in a new window.\n");
 puts("<BR><BR>\n");
 }
 
 
 void printHubListHeader()
 /* Write out the header for a list of hubs in its own table */
 {
 puts("<I>Clicking Connect redirects to the gateway page of the selected hub's default assembly.</I><BR>");
 printf("<table id=\"publicHubsTable\" class=\"hubList\"> "
         "<thead><tr> "
             "<th>Display</th> "
             "<th>Hub Name</th> "
             "<th>Description</th> "
             "<th>Assemblies</th> "
         "</tr></thead></table>\n");
 }
 
 
 struct hash *buildPublicLookupHash(struct sqlConnection *conn, char *publicTable, char *statusTable,
         struct hash **pHash)
 /* Return a hash linking hub URLs to struct hubEntries.  Also make pHash point to a hash that just stores
  * the names of the public hubs (for use later when determining if hubs were added by the user) */
 {
 struct hash *hubLookup = newHash(5);
 struct hash *publicLookup = newHash(5);
 char query[512];
 bool hasDescription = sqlColumnExists(conn, publicTable, "descriptionUrl");
 if (hasDescription)
     sqlSafef(query, sizeof(query), "select p.hubUrl,p.shortLabel,p.longLabel,p.dbList,"
             "s.errorMessage,s.id,p.descriptionUrl from %s p,%s s where p.hubUrl = s.hubUrl", 
 	    publicTable, statusTable); 
 else
     sqlSafef(query, sizeof(query), "select p.hubUrl,p.shortLabel,p.longLabel,p.dbList,"
             "s.errorMessage,s.id from %s p,%s s where p.hubUrl = s.hubUrl", 
 	    publicTable, statusTable); 
 
 struct sqlResult *sr = sqlGetResult(conn, query);
 char **row;
 while ((row = sqlNextRow(sr)) != NULL)
     {
     struct hubEntry *hubInfo = hubEntryTextLoad(row, hasDescription);
     hubInfo->tableHasDescriptionField = hasDescription;
     hashAddUnique(hubLookup, hubInfo->hubUrl, hubInfo);
     hashStore(publicLookup, hubInfo->hubUrl);
     }
 sqlFreeResult(&sr);
 *pHash = publicLookup;
 return hubLookup;
 }
 
 
 void outputPublicTableRow(struct hubEntry *hubInfo, int count)
 /* Prints out a table row with basic information about a hub and a button
  * to connect to that hub */
 {
 int id = hubInfo->id;
 char jsId[256];
 struct slName *dbListNames = slNameListFromComma(hubInfo->dbList);
 printf("<tr>\n");
 if (id != 0)
     {
     ourCellStart();
     char hubName[32];
     safef(hubName, sizeof(hubName), "%s%u", hgHubConnectHubVarPrefix, id);
     if (cartUsualBoolean(cart, hubName, FALSE))
         {
         safef(jsId, sizeof jsId, "hubDisconnectButtonP%d", count);
         printf("<input name=\"hubDisconnectButton\" id='%s' "
             "class=\"hubDisconnectButton\" type=\"button\" value=\"Disconnect\">\n", jsId);
         jsOnEventByIdF("click", jsId, 
             "document.disconnectHubForm.elements['hubId'].value= '%d';"
             "document.disconnectHubForm.submit();return true;", id);
         }
     else
         {
         // get first name off of list of supported databases
         char * name = dbListNames->name;
 
         // if the name isn't currently loaded, we assume it's a hub
         if (!hDbExists(name))
             {
             char buffer[512];
             safef(buffer, sizeof buffer, "hub_%d_%s",  id, name);
             name = cloneString(buffer);
             }
 
         safef(jsId, sizeof jsId, "hubConnectButton%d", count);
         printf("<input name=\"hubConnectButton\" id='%s' "
             "class=\"hubButton\" type=\"button\" value=\"Connect\">\n", jsId);
         jsOnEventByIdF("click", jsId, 
             "document.connectHubForm.elements['hubUrl'].value= '%s';"
             "document.connectHubForm.elements['db'].value= '%s';"
             "document.connectHubForm.submit();return true;", hubInfo->hubUrl,name);
         }
 
     ourCellEnd();
     }
 else
     errAbort("cannot get id for hub with url %s\n", hubInfo->hubUrl);
 
 ourPrintCellLink(hubInfo->shortLabel, hubInfo->hubUrl);
 
 boolean hubHasNoError = isEmpty(hubInfo->errorMessage);
 if (hubHasNoError)
     {
     if (hubInfo->tableHasDescriptionField && !isEmpty(hubInfo->descriptionUrl))
         ourPrintCellLink(hubInfo->longLabel, hubInfo->descriptionUrl);
     else
         ourPrintCell(hubInfo->longLabel);
     }
 else
     {
     ourCellStart();
     printf("<span class=\"hubError\">ERROR: %s </span>"
         "<a href=\"../goldenPath/help/hgTrackHubHelp.html#Debug\">Debug Help</a>", 
         hubInfo->errorMessage);
     safef(jsId, sizeof jsId, "hubClearButton%d", count);
     printf(
     "<input name=\"hubClearButton\" id='%s' "
             "class=\"hubButton\" type=\"button\" value=\"Retry Hub\">"
             , jsId);
     jsOnEventByIdF("click", jsId, 
         "document.resetHubForm.elements['hubCheckUrl'].value='%s';"
         "document.resetHubForm.submit();return true;", hubInfo->hubUrl);
     ourCellEnd();
     }
 
 printGenomeList(hubInfo->hubUrl, dbListNames, count, hubHasNoError); 
 printf("</tr>\n");
 }
 
 struct trackHub *fetchTrackHub(struct hubEntry *hubInfo)
 /* Fetch the hub structure for a public hub, trapping the error
  * if the hub cannot be reached */
 {
 struct errCatch *errCatch = errCatchNew();
 struct trackHub *hub = NULL;
 if (errCatchStart(errCatch))
     {
     char hubName[4096];
     safef(hubName, sizeof(hubName), "hub_%d", hubInfo->id);
     hub = trackHubOpen(hubInfo->hubUrl, hubName);
     }
 errCatchEnd(errCatch);
 if (errCatch->gotError)
     {
     fprintf(stderr, "%s\n", errCatch->message->string);
     }
 errCatchFree(&errCatch);
 return hub;
 }
 
 char *getPositionStringForDb(struct trackHubGenome *genome)
 {
 char positionVar[1024];
 safef(positionVar, sizeof(positionVar), "position.%s", genome->name);
 char *position = cartOptionalString(cart, positionVar);
 if (position == NULL)
     {
     struct dyString *tmp = dyStringCreate("position=");
     if (genome->defaultPos != NULL)
         dyStringAppend(tmp, genome->defaultPos);
     else
         dyStringAppend(tmp, hDefaultPos(genome->name)); // memory leak from hDefaultPos return value
     position = dyStringCannibalize(&tmp);
     }
 return position;
 }
 
 
 struct tdbOutputStructure *hstToTdbOutput(struct hubSearchText *hst, struct genomeOutputStructure *genomeOut, struct trackHub *hub)
 /* Convert a hubSearchText entry to a (list of) tdbOutputStructure(s) */
 {
 struct tdbOutputStructure *tdbOut = hashFindVal(genomeOut->tdbOutHash, hst->track);
 if (tdbOut == NULL)
     {
     genomeOut->trackCount++;
     AllocVar(tdbOut);
     tdbOut->shortLabel = dyStringNew(0);
     tdbOut->metaTags = dyStringNew(0);
     tdbOut->descriptionMatch = dyStringNew(0);
     tdbOut->configUrl = dyStringNew(0);
     dyStringPrintf(tdbOut->shortLabel, "%s", hst->label);
     char *hubId = hubNameFromUrl(hub->url);
     if (isNotEmpty(hst->parents))
         {
         // hst->parents is a comma-sep list like "track1","track1Label","track2","track2Label"
         int i;
         int parentCount;
         int parentTypesCount;
         char *parentTrack = NULL;
         char *parentLabel = NULL;
         char *parentTrackLabels[16]; // 2 slots per parent, can tracks nest more than 8 deep?
         char *parentTypes[16]; // the types of parents, "comp", "super", "view", "other" for each track in parentTrackLabels
         struct tdbOutputStructure *parentTdbOut = NULL;
         struct tdbOutputStructure *savedParent = NULL;
 
         parentCount = chopByCharRespectDoubleQuotes(cloneString(hst->parents), ',', parentTrackLabels, sizeof(parentTrackLabels));
         parentTypesCount = chopCommas(cloneString(hst->parentTypes), parentTypes);
         if (parentCount == 0 || parentCount % 2 != 0)
             {
             errAbort("error parsing hubSearchText->parents for %s.%s in hub: '%s'",
                 genomeOut->genomeName, hst->track, hub->url);
             }
         if (parentTypesCount != (parentCount / 2))
             {
             errAbort("error parsing hubSearchText->parentTypes: '%s' for %s.%s in hub: '%s'",
                 hst->parentTypes, genomeOut->genomeName, hst->track, hub->url);
             }
 
         boolean foundParent = FALSE;
         boolean doAddSaveParent = FALSE;
         for (i = 0; i < parentCount; i += 2)
             {
             parentTrack = stripEnclosingDoubleQuotes(cloneString(parentTrackLabels[i]));
             parentLabel = stripEnclosingDoubleQuotes(cloneString(parentTrackLabels[i+1]));
             // wait until the first valid trackui page for the track hit
             if (isEmpty(dyStringContents(tdbOut->configUrl)) && sameString(parentTypes[i/2], "comp"))
                 {
                 dyStringPrintf(tdbOut->configUrl, "../cgi-bin/hgTrackUi?hubUrl=%s&db=%s&g=%s_%s&hgsid=%s&%s",
                     hub->url, genomeOut->genomeName, hubId, parentTrack, cartSessionId(cart),
                     genomeOut->positionString);
                 }
             else if (isEmpty(dyStringContents(tdbOut->configUrl)) && sameString(parentTypes[i/2], "super"))
                 {
                 dyStringPrintf(tdbOut->configUrl, "../cgi-bin/hgTrackUi?hubUrl=%s&db=%s&g=%s_%s&hgsid=%s&%s",
                     hub->url, genomeOut->genomeName, hubId, hst->track, cartSessionId(cart),
                     genomeOut->positionString);
                 }
             parentTdbOut = hashFindVal(genomeOut->tdbOutHash, parentTrack);
             if (parentTdbOut == NULL)
                 {
                 AllocVar(parentTdbOut);
                 parentTdbOut->shortLabel = dyStringNew(0);
                 parentTdbOut->metaTags = dyStringNew(0);
                 parentTdbOut->descriptionMatch = dyStringNew(0);
                 parentTdbOut->configUrl = dyStringNew(0);
                 // views will be in the parent list, but the &g parameter to trackUi should be the view's parent
                 if (sameString(parentTypes[(i/2)], "view"))
                     dyStringPrintf(parentTdbOut->configUrl,
                         "../cgi-bin/hgTrackUi?hubUrl=%s&db=%s&g=%s_%s&hgsid=%s&%s",
                         hub->url, genomeOut->genomeName, hubId, stripEnclosingDoubleQuotes(parentTrackLabels[(i/2)+2]), cartSessionId(cart), genomeOut->positionString);
                 else // everything else has the correct &g param
                     dyStringPrintf(parentTdbOut->configUrl,
                         "../cgi-bin/hgTrackUi?hubUrl=%s&db=%s&g=%s_%s&hgsid=%s&%s",
                         hub->url, genomeOut->genomeName, hubId, parentTrack, cartSessionId(cart), genomeOut->positionString);
                 dyStringPrintf(parentTdbOut->shortLabel, "%s", parentLabel);
                 parentTdbOut->childCount += 1;
                 if (savedParent)
                     slAddHead(&(parentTdbOut->children), savedParent);
                 else
                     slAddHead(&(parentTdbOut->children), tdbOut);
                 savedParent = parentTdbOut;
                 doAddSaveParent = TRUE;
                 hashAdd(genomeOut->tdbOutHash, parentTrack, parentTdbOut);
                 }
             else
                 {
                 foundParent = TRUE; // don't add this track to the genomeOut->tracks hash again
                 if (savedParent && doAddSaveParent)
                     {
                     parentTdbOut->childCount += 1;
                     slAddHead(&(parentTdbOut->children), savedParent);
                     }
                 else if (!savedParent)
                     {
                     parentTdbOut->childCount += 1;
                     slAddHead(&(parentTdbOut->children), tdbOut);
                     }
                 savedParent = parentTdbOut;
                 doAddSaveParent = FALSE;
                 }
             }
         if (!foundParent)
             {
             slAddHead(&(genomeOut->tracks), parentTdbOut);
             }
         }
     else
         {
         dyStringPrintf(tdbOut->configUrl, "../cgi-bin/hgTrackUi?hubUrl=%s&db=%s&g=%s_%s&hgsid=%s&%s",
             hub->url, genomeOut->genomeName, hubId, hst->track, cartSessionId(cart),
             genomeOut->positionString);
         slAddHead(&(genomeOut->tracks), tdbOut);
         }
     hashAdd(genomeOut->tdbOutHash, hst->track, tdbOut);
     }
 return tdbOut;
 }
 
 struct hubOutputStructure *buildHubSearchOutputStructure(struct trackHub *hub,
         struct hubSearchText *searchResults)
 /* Build a structure that contains the data for writing out the hub search results for this hub */
 {
 struct hash *missingGenomes = hashNew(0);
 struct hubOutputStructure *hubOut = NULL;
 AllocVar(hubOut);
 hubOut->metaTags = dyStringNew(0);
 hubOut->descriptionMatch = dyStringNew(0);
 hubOut->genomeOutHash = newHash(5);
 
 
 struct hubSearchText *hst = NULL;
 for (hst = searchResults; hst != NULL; hst = hst->next)
     {
     if (isEmpty(hst->db))
         {
         // must be a hit to the hub itself, not an assembly or track within it
         if (hst->textLength == hubSearchTextLong)
             {
             dyStringPrintf(hubOut->descriptionMatch, "%s", hst->text);
             }
         else if (hst->textLength == hubSearchTextMeta)
             {
             if (isNotEmpty(dyStringContents(hubOut->metaTags)))
                 dyStringPrintf(hubOut->metaTags, ", %s", hst->text);
             else
                 dyStringPrintf(hubOut->metaTags, "%s", hst->text);
             }
         continue;
         }
 
     char *db = cloneString(hst->db);
     if (hashLookup(missingGenomes, db) != NULL)
         continue;
     struct trackHubGenome *genome = hashFindVal(hub->genomeHash, db);
     if (genome == NULL)
         {
         // assembly hub genomes are stored with a prefix; try that
         char withHubName[4096];
         safef(withHubName, sizeof(withHubName), "%s_%s", hub->name, db);
         genome = hashFindVal(hub->genomeHash, withHubName);
         if (genome == NULL)
             {
             hashStoreName(missingGenomes, db);
             warn("Error: Unable to find info for matching assembly '%s'.  Skipping ...\n", withHubName);
             continue;
             }
         }
     struct genomeOutputStructure *genomeOut = hashFindVal(hubOut->genomeOutHash, db);
     if (genomeOut == NULL)
         {
         AllocVar(genomeOut);
         genomeOut->tdbOutHash = newHash(5);
         genomeOut->metaTags = dyStringNew(0);
         genomeOut->descriptionMatch = dyStringNew(0);
         genomeOut->shortLabel = dyStringNew(0);
         genomeOut->assemblyLink = dyStringNew(0);
         genomeOut->positionString = getPositionStringForDb(genome);
         dyStringPrintf(genomeOut->assemblyLink, "../cgi-bin/hgTracks?hubUrl=%s&db=%s&hgsid=%s&%s",
                 hub->url, genome->name, cartSessionId(cart), genomeOut->positionString);
         char *name = trackHubSkipHubName(genome->name);
         if (isNotEmpty(genome->description))
             dyStringPrintf(genomeOut->shortLabel, "%s (%s)", genome->description, name);
         else if (isNotEmpty(genome->organism))
             dyStringPrintf(genomeOut->shortLabel, "%s %s", genome->organism, name);
         else
             dyStringPrintf(genomeOut->shortLabel, "%s", name);
         genomeOut->genomeName = cloneString(genome->name);
         hashAdd(hubOut->genomeOutHash, db, genomeOut);
         slAddTail(&(hubOut->genomes), genomeOut);
         hubOut->genomeCount++;
         }
     if (isEmpty(hst->track))
         {
         if (hst->textLength == hubSearchTextLong) // Genome description match
             dyStringPrintf(genomeOut->descriptionMatch, "%s", hst->text);
         else if (hst->textLength == hubSearchTextMeta)
             {
             if (isNotEmpty(dyStringContents(genomeOut->metaTags)))
                 dyStringPrintf(genomeOut->metaTags, ", %s", hst->text);
             else
                 dyStringPrintf(genomeOut->metaTags, "%s", hst->text);
             }
         }
 
     if (isNotEmpty(hst->track))
         {
         // Time to add a track! (or add info to one, maybe)
         struct tdbOutputStructure *tdbOut = hstToTdbOutput(hst, genomeOut, hub);
         if (tdbOut != NULL)
             {
             if (hst->textLength == hubSearchTextLong)
                 dyStringPrintf(tdbOut->descriptionMatch, "%s", hst->text);
             else if (hst->textLength == hubSearchTextMeta)
                 {
                 if (isNotEmpty(dyStringContents(tdbOut->metaTags)))
                     dyStringPrintf(tdbOut->metaTags, ", %s", hst->text);
                 else
                     dyStringPrintf(tdbOut->metaTags, "%s", hst->text);
                 }
             }
         }
     }
 return hubOut;
 }
 
 static char *tdbOutputStructureLabelToId(struct tdbOutputStructure *tdbOut)
 /* Make an array name out of a tdbOutputStruct */
 {
 struct dyString *id = dyStringNew(0);
 dyStringPrintf(id, "%s", htmlEncode(dyStringContents(tdbOut->shortLabel)));
 if (tdbOut->childCount > 0)
     {
     dyStringPrintf(id, " (%d subtrack%s)", tdbOut->childCount,
         tdbOut->childCount == 1 ? "" : "s");
     }
 return dyStringCannibalize(&id);
 }
 
 static void printTdbOutputStructureToDyString(struct tdbOutputStructure *tdbOut, struct dyString *dy, char *arrayName)
 /* Print a tdbOutputStructure to a dyString, recursive for subtracks. */
 {
 dyStringPrintf(dy, "trackData['%s'] = [", arrayName);
 
 if (tdbOut->childCount > 0)
     {
     struct dyString *subtrackDy = dyStringNew(0);
     struct tdbOutputStructure *child = tdbOut->children;
     while (child != NULL)
         {
         char *childId = tdbOutputStructureLabelToId(child);
         dyStringPrintf(dy, "\n\t{\n\tid: '%s',\n\tparent: '%s',\n\t"
             "li_attr: {nodetype:'track', configlink:'%s'},\n\ttext: \'%s ",
             childId, arrayName, dyStringContents(child->configUrl), childId);
         if (isNotEmpty(dyStringContents(child->metaTags)))
             {
             dyStringPrintf(dy, "<br><span class=\\'descriptionMatch\\'><em>Metadata: %s</em></span>",
                 htmlEncode(dyStringContents(child->metaTags)));
             }
         if (isNotEmpty(dyStringContents(child->descriptionMatch)))
             {
             dyStringPrintf(dy, "<br><span class=\\'descriptionMatch\\'><em>Description: %s</em></span>",
                 htmlEncode(dyStringContents(child->descriptionMatch)));
             }
         dyStringPrintf(dy, "\'");
         if (child->childCount > 0)
             {
             dyStringPrintf(dy, ",\n\tchildren: true");
             printTdbOutputStructureToDyString(child, subtrackDy, childId);
             }
         dyStringPrintf(dy, "\n\t},");
         child = child->next;
         }
     dyStringPrintf(dy, "];\n");
     if (isNotEmpty(dyStringContents(subtrackDy)))
         dyStringPrintf(dy, "%s", subtrackDy->string);
     }
 else
     dyStringPrintf(dy, "];\n");
 }
 
 void printGenomeOutputStructureToDyString(struct genomeOutputStructure *genomeOut, struct dyString *dy, char *genomeNameId)
 /* Print a genomeOutputStructure to a dyString. The structure here is:
  * trackData[genome] = [{track 1 obj}, {track2 obj}, {track3 obj}, ... ]
  * trackData[track1] = [{search hit text}, {subtrack1 obj}, {subtrack2 obj}, ... ]
  *
  * if track1, track2, track3 are container tracks, then the recursive function
  * tdbOutputStructureToDystring creates the above trackData[track1] = [{}] for
  * each of the containers, otherwise a single child of the genome is sufficient */
 {
 struct tdbOutputStructure *tdbOut = NULL;
 static  struct dyString *tdbArrayDy = NULL; // the dyString for all of the tdb objects
 static struct dyString *idString = NULL; // the special id of this track
 if (tdbArrayDy == NULL)
     tdbArrayDy = dyStringNew(0);
 if (idString == NULL)
     idString = dyStringNew(0);
 
 dyStringPrintf(dy, "trackData['%s'] = [", genomeNameId);
 if (genomeOut->tracks != NULL)
     {
     tdbOut = genomeOut->tracks;
     slReverse(&tdbOut);
     while (tdbOut != NULL)
         {
         dyStringPrintf(idString, "%s", tdbOutputStructureLabelToId(tdbOut));
         dyStringPrintf(dy, "\n\t{\n\t'id': '%s',\n\t'parent': '%s',\n\t"
             "'li_attr': {'nodetype':'track', configlink: '%s'},\n\t'text': \'%s ",
             idString->string, genomeNameId, dyStringContents(tdbOut->configUrl), idString->string);
         if (isNotEmpty(dyStringContents(tdbOut->metaTags)))
             {
             dyStringPrintf(dy, "<br><span class=\\'descriptionMatch\\'><em>Metadata: %s</em></span>",
                 htmlEncode(dyStringContents(tdbOut->metaTags)));
             }
         if (isNotEmpty(dyStringContents(tdbOut->descriptionMatch)))
             {
             dyStringPrintf(dy, "<br><span class=\\'descriptionMatch\\'><em>Description: %s</em></span>",
                 htmlEncode(dyStringContents(tdbOut->descriptionMatch)));
             }
         dyStringPrintf(dy, "\'");
 
         // above we took care of both non-heirarchical tracks and the top-level containers,
         // now do container children, which also takes care of any deeper heirarchies
         if (tdbOut->childCount > 0)
             dyStringPrintf(dy, ",\n\t'children': true");
         dyStringPrintf(dy, "\n\t},\n");
 
         if (tdbOut->childCount > 0)
             printTdbOutputStructureToDyString(tdbOut, tdbArrayDy, idString->string);
         tdbOut = tdbOut->next;
         dyStringClear(idString);
         }
     }
 dyStringPrintf(dy, "];\n"); // close off genome node
 dyStringPrintf(dy, "%s\n", tdbArrayDy->string);
 dyStringClear(tdbArrayDy);
 }
 
 void printHubOutputStructure(struct hubOutputStructure *hubOut, struct hubEntry *hubInfo)
 /* Convert a hubOutputStructure to a jstree-readable string. This function forms the root
  * node for each hub search tree, whose children are the hub description match and each individual
  * genome node. A simplified structure is:
  * trackData['#_hubId'] = [{id:'descriptionMatch',...},{id:'assembly1',...},...]
  *
  * The id's become new "trackData[id]" entries with their own arrays later if they have
  * sub-trees (via printGenomeOutputStructureToDyString() and printTdbOutputStructureToDyString(). */
 {
 struct dyString *dy = dyStringNew(0);
 // The leading '#' tells the javascript this is a 'root' node
 dyStringPrintf(dy, "trackData['#_%d'] = [", hubInfo->id);
 if (isNotEmpty(dyStringContents(hubOut->descriptionMatch)))
     {
     dyStringPrintf(dy, "{'id':'%d_descriptionMatchText','parent':'#_%d',"
         "'state':{'opened': true},'text': 'Hub Description: "
         "<span class=\"descriptionMatch\"><em>%s</em></span>'},",
         hubInfo->id, hubInfo->id, htmlEncode(dyStringContents(hubOut->descriptionMatch)));
     }
 struct genomeOutputStructure *genomeOut = hubOut->genomes;
 struct dyString *genomeDy = dyStringNew(0);
 if (genomeOut != NULL)
     {
     dyStringPrintf(dy, "{'id':'%d_assemblies', 'text':'%d Matching Assembl%s', 'parent':'#_%d', "
         "'children':true, 'li_attr': {'state':{'opened': 'false'}}}];\n",
         hubInfo->id, hubOut->genomeCount, hubOut->genomeCount == 1 ? "y" : "ies", hubInfo->id);
     dyStringPrintf(dy, "trackData['%d_assemblies'] = [", hubInfo->id);
 
     while (genomeOut != NULL)
         {
         char *assemblyName = htmlEncode(dyStringContents(genomeOut->shortLabel));
         char genomeNameId[512];
         safef(genomeNameId, sizeof(genomeNameId), "%d_%s", hubInfo->id, assemblyName);
         dyStringPrintf(dy, "{'id': '%s', 'parent': '%d_assemblies', 'children': true, "
             "'li_attr': {'assemblylink': '%s','nodetype': 'assembly'},"
             "'text': \"%s",
             genomeNameId, hubInfo->id, dyStringContents(genomeOut->assemblyLink), assemblyName);
         if (genomeOut->trackCount > 0)
             {
             dyStringPrintf(dy, " (%d track%s) ", genomeOut->trackCount,
                 genomeOut->trackCount == 1 ? "" : "s");
             }
         if (isNotEmpty(dyStringContents(genomeOut->metaTags)))
             {
             dyStringPrintf(dy, "<br><span class='descriptionMatch'><em>%s</em></span>",
                 htmlEncode(dyStringContents(genomeOut->metaTags)));
             }
         if (isNotEmpty(dyStringContents(genomeOut->descriptionMatch)))
             {
             dyStringPrintf(dy, "<br><em>Assembly Description:</em> %s",
                 htmlEncode(dyStringContents(genomeOut->descriptionMatch)));
             }
         dyStringPrintf(dy, "\"},");
         printGenomeOutputStructureToDyString(genomeOut, genomeDy, genomeNameId);
         genomeOut = genomeOut->next;
         }
     }
 dyStringPrintf(dy, "];\n");
 dyStringPrintf(dy, "%s", genomeDy->string);
 jsInline(dy->string);
 dyStringClear(dy);
 }
 
 static void printOutputForHub(struct hubEntry *hubInfo, struct hubSearchText *hubSearchResult, int count)
 /* Given a hub's info and a structure listing the search hits within the hub, first print
  * a basic line of hub information with a "connect" button.  Then, if the search results
  * are non-NULL, write out information about the genomes and tracks from the search hits that
  * match the db filter.
  * If there are no search results to print, the basic hub lines are combined into a single HTML table
  * that is defined outside this function.
  * Otherwise, each hub line is printed in its own table followed by a <ul> containing details
  * about the search results. */
 {
 if (hubSearchResult != NULL)
     printf("<table class='hubList'><tbody>\n");
 outputPublicTableRow(hubInfo, count);
 if (hubSearchResult != NULL)
     {
     printf("</tbody></table>\n");
+    struct trackHub *hub = fetchTrackHub(hubInfo);
+    if (hub != NULL)
+        {
         printf("<div class=\"hubTdbTree\">\n");
         printf("<div id='tracks%d'></div>", hubInfo->id); // div for the jstree for this hub's search result(s)
         printf("</div>\n");
-    struct trackHub *hub = fetchTrackHub(hubInfo);
         struct hubOutputStructure *hubOut = buildHubSearchOutputStructure(hub, hubSearchResult);
         if (dyStringIsEmpty(hubOut->descriptionMatch) && (hubOut->genomes == NULL))
             return; // no detailed search results; hit must have been to hub short label or something
         printHubOutputStructure(hubOut, hubInfo);
         }
     }
+}
 
 int hubEntryCmp(const void *va, const void *vb)
 /* Compare to sort based on shortLabel */
 {
 const struct hubEntry *a = *((struct hubEntry **)va);
 const struct hubEntry *b = *((struct hubEntry **)vb);
 
 return strcasecmp(a->shortLabel, b->shortLabel);
 }
 
 
 void printHubList(struct slName *hubsToPrint, struct hash *hubLookup, struct hash *searchResultHash)
 /* Print out a list of hubs, possibly along with search hits to those hubs.
  * hubLookup takes hub URLs to struct hubEntry
  * searchResultHash takes hub URLs to struct hubSearchText * (list of hits on that hub)
  */
 {
 int count = 0;
-int udcTimeoutVal = udcCacheTimeout();
-char *udcOldDir = cloneString(udcDefaultDir());
-char *searchUdcDir = cfgOptionDefault("hgHubConnect.cacheDir", udcOldDir);
-udcSetDefaultDir(searchUdcDir);
-udcSetCacheTimeout(1<<30);
 struct hubEntry *hubList = NULL;
 struct hubEntry *hubInfo;
 long slTime;
 long printOutputForHubTime;
 boolean measureTiming = cartUsualBoolean(cart, "measureTiming", FALSE);
 if (hubsToPrint != NULL)
     {
     printHubListHeader();
 
     if (searchResultHash == NULL) // if not displaying search results, join the hub <tr>s into one table
         printf("<table class='hubList'><tbody>\n");
     struct slName *thisHubName = NULL;
     for (thisHubName = hubsToPrint; thisHubName != NULL; thisHubName = thisHubName->next)
         {
         hubInfo = (struct hubEntry *) hashFindVal(hubLookup, thisHubName->name);
         if (hubInfo == NULL)
             {
             /* This shouldn't happen often, but maybe the search hits list was built from an outdated
              * search text file that includes hubs for which no info is available.
              * Skip this hub. */
             continue;
             }
         slAddHead(&hubList, hubInfo);
         }
     slSort(&hubList, hubEntryCmp);
     slTime = clock1000();
 
     for (hubInfo = hubList; hubInfo != NULL; hubInfo = hubInfo->next)
         {
         struct hubSearchText *searchResult = NULL;
         if (searchResultHash != NULL)
             {
             searchResult = (struct hubSearchText *) hashMustFindVal(searchResultHash, hubInfo->hubUrl);
             }
         printOutputForHub(hubInfo, searchResult, count);
         count++;
         }
     printOutputForHubTime = clock1000();
     if (measureTiming)
         printf("hgHubConnect: printOutputForHubTime before js execution: %lu millis<BR>\n", printOutputForHubTime - slTime);
     if (searchResultHash == NULL)
         printf("</tbody></table>\n");
     }
-udcSetCacheTimeout(udcTimeoutVal);
-udcSetDefaultDir(udcOldDir);
 if (hubsToPrint != NULL)
     {
     /* Write out the list of hubs in a single table inside a div that will be hidden by
      * javascript.  This table is used (before being hidden) to set common column widths for
      * the individual hub tables when they're split by detailed search results. */
     printf("<div id='hideThisDiv'>\n");
     printf("<table class='hubList' id='hideThisTable'><tbody>\n");
     for (hubInfo = hubList; hubInfo != NULL; hubInfo = hubInfo->next)
         {
         printOutputForHub(hubInfo, NULL, count);
         count++;
         }
     printf("</tbody></table>\n");
     printf("</div>\n");
     }
 jsInline(
         "function lineUpCols()\n"
         "    {\n"
         "    var tableList = $('table.hubList');\n"
         "    if (tableList.length == 0)\n"
         "        return;\n"
         "    var colWidths = new Array();\n"
         "    var combinedTrackTable = $('#hideThisTable');\n"
         "    for (i=0; i<combinedTrackTable[0].rows[0].cells.length; i++)\n"
         "        colWidths[i] = combinedTrackTable[0].rows[0].cells[i].clientWidth;\n"
         "    $('#hideThisDiv')[0].style.display = 'none';\n"
         "    for(i=0; i<tableList.length; i++)\n"
         "        {\n"
         "        for(j=0; j<tableList[i].rows[0].cells.length; j++)\n"
         "            tableList[i].rows[0].cells[j].style.width = colWidths[j]+'px';\n"
         "        }\n"
         "    }\n"
         "window.onload = lineUpCols();\n"
         );
 if (searchResultHash != NULL)
     jsInline("hubSearchTree.init(true);\n");
 }
 
 
 static bool outputPublicTable(struct sqlConnection *conn, char *publicTable, char *statusTable,
         struct hash **pHash)
 /* Put up the list of public hubs and other controls for the page. */
 {
 char *hubSearchTerms = cartOptionalString(cart, hgHubSearchTerms);
 char *dbFilter = cartOptionalString(cart, hgHubDbFilter);
 char *lcDbFilter = cloneString(dbFilter);
 if (isNotEmpty(lcDbFilter))
     tolowers(lcDbFilter);
 
 // make sure all the public hubs are in the hubStatus table.
 addPublicHubsToHubStatus(conn, publicTable, statusTable);
 
 // build full public hub lookup hash, taking each URL to struct hubEntry * for that hub
 struct hash *hubLookup = buildPublicLookupHash(conn, publicTable, statusTable, pHash);
 
 printf("<div id=\"publicHubs\" class=\"hubList\"> \n");
 
 char *hubSearchTableName = cfgOptionDefault("hubSearchTextTable", "hubSearchText");
 int searchEnabled = sqlTableExists(conn, hubSearchTableName);
 
 printSearchAndFilterBoxes(searchEnabled, hubSearchTerms, dbFilter);
 
 struct hash *searchResultHash = NULL;
 struct slName *hubsToPrint = NULL;
 if (searchEnabled && !isEmpty(hubSearchTerms))
     {
     printSearchTerms(hubSearchTerms);
     // Forcing checkDescriptions to TRUE right now, but we might want to add this as a
     // checkbox option for users in the near future.
     bool checkDescriptions = TRUE;
     struct hubSearchText *hubSearchResults = getHubSearchResults(conn, hubSearchTableName,
             hubSearchTerms, checkDescriptions, lcDbFilter, hubLookup);
     searchResultHash = newHash(5);
     struct hubSearchText *hst = hubSearchResults;
     while (hst != NULL)
         {
         struct hubSearchText *nextHst = hst->next;
         hst->next = NULL;
         struct hashEl *hubHashEnt = hashLookup(searchResultHash, hst->hubUrl);
         if (hubHashEnt == NULL)
             {
             slNameAddHead(&hubsToPrint, hst->hubUrl);
             hashAdd(searchResultHash, hst->hubUrl, hst);
             }
         else
             slAddHead(&(hubHashEnt->val), hst);
         hst = nextHst;
         }
     struct hashEl *hel;
     struct hashCookie cookie = hashFirst(searchResultHash);
     while ((hel = hashNext(&cookie)) != NULL)
         slReverse(&(hel->val));
     }
 else
     {
     // There is no active search, so just add all hubs to the list
     struct hashEl *hel;
     struct hashEl *helList;
     helList = hashElListHash(hubLookup);
     for (hel = helList; hel != NULL; hel = hel->next)
         {
         if (isNotEmpty(lcDbFilter))
             {
             struct hubEntry *hubEnt = (struct hubEntry *) hel->val;
             char *lcDbList = cloneString(hubEnt->dbList);
             if (isNotEmpty(lcDbList))
                 tolowers(lcDbList);
             if ((lcDbList == NULL) || (stringIn(lcDbFilter, lcDbList) == NULL))
                 continue;
             }
         slNameAddHead(&hubsToPrint, hel->name);
         }
     }
 slReverse(&hubsToPrint);
 
 printHubList(hubsToPrint, hubLookup, searchResultHash);
 printf("</div>");
 return (hubsToPrint != NULL);
 }
 
 
 struct hash *hgHubConnectPublic()
 /* Put up the list of public hubs and other controls for the page. */
 {
 struct hash *retHash = NULL;
 struct sqlConnection *conn = hConnectCentral();
 char *publicTable = cfgOptionEnvDefault("HGDB_HUB_PUBLIC_TABLE", 
 	hubPublicTableConfVariable, defaultHubPublicTableName);
 char *statusTable = cfgOptionEnvDefault("HGDB_HUB_STATUS_TABLE", 
 	hubStatusTableConfVariable, defaultHubStatusTableName);
 if (!(sqlTableExists(conn, publicTable) && 
 	outputPublicTable(conn, publicTable,statusTable, &retHash)) )
     {
     printf("<div id=\"publicHubs\" class=\"hubList\"> \n");
     printf("No Public Track Hubs found that match search criteria.<BR>");
     printf("</div>");
     }
 hDisconnectCentral(&conn);
 
 return retHash;
 }
 
 
 static void tryHubOpen(unsigned id)
 /* try to open hub, leaks trackHub structure */
 {
 /* try opening this again to reset error */
 struct sqlConnection *conn = hConnectCentral();
 struct errCatch *errCatch = errCatchNew();
 struct hubConnectStatus *hub = NULL;
 if (errCatchStart(errCatch))
     hub = hubConnectStatusForId(conn, id);
 errCatchEnd(errCatch);
 if (errCatch->gotError)
     hubUpdateStatus( errCatch->message->string, NULL);
 else
     hubUpdateStatus(NULL, hub);
 errCatchFree(&errCatch);
 
 hDisconnectCentral(&conn);
 }
 
 
 static int doRedirect(struct cart *theCart)
 {
 struct hubConnectStatus *hub = hubConnectNewHub();
 if (hub == NULL)
     return 0;
 
 char headerText[1024];
 
 char *errorMessage;
 hubFindOrAddUrlInStatusTable(database, cart, hub->hubUrl, &errorMessage);
 
 // if there is an error message, we stay in hgHubConnect
 if (errorMessage != NULL)
     return 0;
 
 getDbAndGenome(cart, &database, &organism, oldVars);
 
 int redirDelay = 3;
 printf( "<META HTTP-EQUIV=\"REFRESH\" CONTENT=\"%d;URL=%s?%s\">",
 	  redirDelay,"../cgi-bin/hgGateway",cartSidUrlString(cart));
 safef(headerText, sizeof(headerText), "Hub Connect Successful");
 cartWebStart(cart, NULL, "%s", headerText);
 
 hPrintf("You will be automatically redirected to the gateway page for this hub's default database "
     "(<A HREF=\"../cgi-bin/hgGateway?%s\">%s</A>) in %d seconds.<BR><BR>",
 	  cartSidUrlString(cart),trackHubSkipHubName(database),redirDelay);
 
 struct trackHub *tHub = hub->trackHub;
 if (tHub->email != NULL)
     {
     hPrintf("<B>This hub is provided courtesy of <A HREF=\"mailto:%s\">%s</A>.</B> Please contact them with any questions.", tHub->email, tHub->email);
     }
 
 hPrintf("<BR><BR>");
 hPrintf("Hub: %s<BR><BR>", tHub->longLabel);
 hPrintf("Hub Genomes: ");
 struct trackHubGenome *genomeList = tHub->genomeList;
 
 bool firstTime = TRUE;
 for(; genomeList; genomeList = genomeList->next)
     {
     if (!firstTime)
 	hPrintf(",");
     firstTime = FALSE;
     hPrintf("<A href=\"../cgi-bin/hgTracks?db=%s&%s\">%s</A>",genomeList->name, 
 	cartSidUrlString(cart),trackHubSkipHubName(genomeList->name));
     }
 hPrintf("<BR><BR>");
 return 1;
 }
 
 static void doResetHub(struct cart *theCart)
 {
 char *url = cartOptionalString(cart, hgHubCheckUrl);
 
 if (url != NULL)
     {
     udcSetCacheTimeout(1);
     unsigned id = hubResetError(url);
     tryHubOpen(id);
     }
 else
     errAbort("must specify url in %s\n", hgHubDataText);
 }
 
 static void doClearHub(struct cart *theCart)
 {
 char *url = cartOptionalString(cart, hgHubDataText);
 
 printf("<pre>clearing hub %s\n",url);
 if (url != NULL)
     hubClearStatus(url);
 else
     errAbort("must specify url in %s\n", hgHubDataText);
 printf("<pre>Completed\n");
 }
 
 
 static void checkTrackDbs(struct hubConnectStatus *hubList)
 {
 struct hubConnectStatus *hub = hubList;
 
 for(; hub; hub = hub->next)
     {
     struct errCatch *errCatch = errCatchNew();
     if (errCatchStart(errCatch))
 	{
 	hubAddTracks(hub, database);
 	}
     errCatchEnd(errCatch);
     if (errCatch->gotError)
 	{
 	hub->errorMessage = cloneString(errCatch->message->string);
 	hubUpdateStatus( errCatch->message->string, hub);
 	}
     else
 	hubUpdateStatus(NULL, hub);
     }
 }
 
 int hubConnectStatusCmp(const void *va, const void *vb)
 /* Compare to sort based on shortLabel */
 {
 const struct hubConnectStatus *a = *((struct hubConnectStatus **)va);
 const struct hubConnectStatus *b = *((struct hubConnectStatus **)vb);
 struct trackHub *ta = a->trackHub;
 struct trackHub *tb = b->trackHub;
 
 if ((ta == NULL) || (tb == NULL))
     return 0;
 
 return strcasecmp(tb->shortLabel, ta->shortLabel);
 }
 
 void doMiddle(struct cart *theCart)
 /* Write header and body of html page. */
 {
 cart = theCart;
 
 if(cgiIsOnWeb())
     checkForGeoMirrorRedirect(cart);
 
 if (cartVarExists(cart, hgHubDoClear))
     {
     doClearHub(cart);
     cartWebEnd();
     return;
     }
 
 if (cartVarExists(cart, hgHubCheckUrl))
     {
     doResetHub(cart);
     }
 
 if (cartVarExists(cart, hgHubDoRedirect))
     {
     if (doRedirect(cart))
 	{
 	cartWebEnd();
 	return;
 	}
     }
 
 cartWebStart(cart, NULL, "%s", pageTitle);
 
 printf(
 "<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.7/themes/default/style.min.css\" />\n"
 "<script src=\"https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.1/jquery.min.js\"></script>\n"
 "<script src=\"https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.7/jstree.min.js\"></script>\n"
 "<style>.jstree-default .jstree-anchor { height: initial; } </style>\n"
 );
 jsIncludeFile("utils.js", NULL);
 jsIncludeFile("jquery-ui.js", NULL);
 webIncludeResourceFile("jquery-ui.css");
 jsIncludeFile("ajax.js", NULL);
 jsIncludeFile("hgHubConnect.js", NULL);
 webIncludeResourceFile("hgHubConnect.css");
 jsIncludeFile("jquery.cookie.js", NULL);
 jsIncludeFile("spectrum.min.js", NULL);
 
 boolean doHubDevMode = cfgOptionBooleanDefault("hgHubConnect.validateHub", FALSE);
 printf("<div id=\"hgHubConnectUI\"> <div id=\"description\"> \n");
 printf(
     "<P>Track data hubs are collections of external tracks that can be imported into the UCSC Genome Browser. "
     "Hub tracks show up under the hub's own blue label bar on the main browser page, "
     "as well as on the configure page. For more information, including where to "
     "<A HREF=\"../goldenPath/help/hgTrackHubHelp.html#Hosting\">host</A> your track hub, see the "
     "<A HREF=\"../goldenPath/help/hgTrackHubHelp.html\" TARGET=_blank>"
     "User's Guide</A>."
     "To import a public hub click its \"Connect\" button below.</P>"
     "<P><B>NOTE: Because Track Hubs are created and maintained by external sources,"
     " UCSC is not responsible for their content.</B></P>"
    );
 printf("</div>\n");
 if (doHubDevMode)
     {
     printf("<div id=\"developerModeDescription\" style=\"display:none\"> \n");
     printf("<p>Check the configuration settings for a hub, including the settings "
         "in the hub.txt, genomes.txt, and trackDb.txt files. If there are errors in the hub "
         "setup, a heirarchal tree of tracks is presented with the errors in red text. A hub "
         "with no errors still has the tree present but needs to be clicked to be expanded to "
         " explore the track heirarchy.</p>\n"
         "<p>Also present are buttons to load the hub with track timing measuring turned on "
         "in order to profile slow loading tracks, and an option to disable the track caching "
         "mechanism so the Genome Browser picks up changes to the configuration files or "
         "track data files. For more information on making track hubs, please see the following links: \n "
         "<ul>\n"
         "<li><a href=\"../goldenPath/help/trackDb/trackDbHub.html\">Track Hub Settings</a></li> \n"
         "<li><a href=\"../goldenPath/help/hgTrackHubHelp#Hosting\">Where to host your track hub</a></li> \n"
         "</ul> \n"
         "</div>\n "
         );
     }
 
 // this variable is used by hub search and hub validate, initialize here so we don't
 // overwrite it unintentionally depending on which path the CGI takes
 jsInline("trackData = [];\n");
 
 getDbAndGenome(cart, &database, &organism, oldVars);
 
 char *survey = cfgOptionEnv("HGDB_HUB_SURVEY", "hubSurvey");
 char *surveyLabel = cfgOptionEnv("HGDB_HUB_SURVEY_LABEL", "hubSurveyLabel");
 
 if (survey && differentWord(survey, "off"))
     hPrintf("<span style='background-color:yellow;'><A HREF='%s' TARGET=_BLANK><EM><B>%s</EM></B></A></span>\n", survey, surveyLabel ? surveyLabel : "Take survey");
 hPutc('\n');
 
 // grab all the hubs that are listed in the cart
 struct hubConnectStatus *hubList =  hubConnectStatusListFromCartAll(cart);
 
 checkTrackDbs(hubList);
 
 slSort(&hubList, hubConnectStatusCmp);
 
 // here's a little form for the add new hub button
 printf("<FORM ACTION=\"%s\" NAME=\"addHubForm\">\n",  "../cgi-bin/hgHubConnect");
 cgiMakeHiddenVar("hubUrl", "");
 cgiMakeHiddenVar( hgHubDoFirstDb, "on");
 cgiMakeHiddenVar( hgHubDoRedirect, "on");
 cgiMakeHiddenVar(hgHubConnectRemakeTrackHub, "on");
 cartSaveSession(cart);
 puts("</FORM>");
 
 // this is the form for the connect hub button
 printf("<FORM ACTION=\"%s\" NAME=\"connectHubForm\">\n",  "../cgi-bin/hgHubConnect");
 cgiMakeHiddenVar("hubUrl", "");
 cgiMakeHiddenVar("db", "");
 cgiMakeHiddenVar( hgHubDoRedirect, "on");
 cgiMakeHiddenVar(hgHubConnectRemakeTrackHub, "on");
 cartSaveSession(cart);
 puts("</FORM>");
 
 // this is the form for the disconnect hub button
 printf("<FORM ACTION=\"%s\" NAME=\"disconnectHubForm\">\n",  "../cgi-bin/hgHubConnect");
 cgiMakeHiddenVar("hubId", "");
 cgiMakeHiddenVar(hgHubDoDisconnect, "on");
 cgiMakeHiddenVar(hgHubConnectRemakeTrackHub, "on");
 cartSaveSession(cart);
 puts("</FORM>");
 
 // this is the form for the reset hub button
 printf("<FORM ACTION=\"%s\" NAME=\"resetHubForm\">\n",  "../cgi-bin/hgHubConnect");
 cgiMakeHiddenVar(hgHubCheckUrl, "");
 cartSaveSession(cart);
 puts("</FORM>");
 
 // this is the form for the search hub button
 printf("<FORM ACTION=\"%s\" NAME=\"searchHubForm\">\n",  "../cgi-bin/hgHubConnect");
 cgiMakeHiddenVar(hgHubSearchTerms, "");
 cgiMakeHiddenVar(hgHubDoSearch, "on");
 cgiMakeHiddenVar(hgHubDbFilter, "");
 cartSaveSession(cart);
 puts("</FORM>");
 
 // this is the form for the validate button
 if (doHubDevMode)
     {
     printf("<FORM ACTION=\"%s\" NAME=\"validateHubForm\">\n",  "../cgi-bin/hgHubConnect");
     cgiMakeHiddenVar("validateHubUrl", "");
     cgiMakeHiddenVar("measureTiming", "");
     cgiMakeHiddenVar("udcTimeout", "");
     // allows saving the old url in the search bar but not run hubCheck on it right away
     // mostly for when you come back to hgHubConnect after just looking at hgTracks
     cgiMakeHiddenVar(hgHubDoHubCheck, "off");
     cartSaveSession(cart);
     puts("</FORM>");
     }
 
 // ... and now the main form
 printf("<FORM ACTION=\"%s\" METHOD=\"POST\" NAME=\"mainForm\">\n", "../cgi-bin/hgGateway");
 cartSaveSession(cart);
 
 // we have two tabs for the public and unlisted hubs
 printf("<div id=\"tabs\">"
        "<ul> <li><a class=\"defaultDesc\" href=\"#publicHubs\">Public Hubs</a></li>"
        "<li><a class=\"defaultDesc\" href=\"#unlistedHubs\">My Hubs</a></li> ");
 if (doHubDevMode) // put up the validate tab if hg.conf statement present
     printf("<li><a class=\"hubDeveloperDesc\" href=\"#hubDeveloper\">Hub Development</a></li>");
 printf("</ul> ");
 
 struct hash *publicHash = hgHubConnectPublic();
 hgHubConnectUnlisted(hubList, publicHash);
 if (doHubDevMode)
     hgHubConnectDeveloperMode();
 // add the event listener for toggling the right description text
 jsInline(
     "var oldDesc = document.getElementById('description');\n"
     "var newDesc = document.getElementById('developerModeDescription');\n"
     "if (location.hash === \"#hubDeveloper\") {\n"
     "   toggleTwoDivs(newDesc, oldDesc);\n"
     "}\n"
     "window.addEventListener('hashchange', function() {\n"
     "   if (location.hash === \"#hubDeveloper\") {"
     "       toggleTwoDivs(newDesc, oldDesc);\n"
     "   } else {\n"
     "       toggleTwoDivs(oldDesc, newDesc);\n"
     "   }\n"
     "});\n"
     "var plusButton = document.getElementById('advancedSettingsButton');\n"
     "plusButton.addEventListener('click', function() {\n"
     "   var advancedSettingsDiv = document.getElementById('extraSettingsContainer');\n"
     "   var extraSettingsList = document.getElementById('extraSettingsList');\n"
     "   if (extraSettingsList.style.display != 'none') {\n"
     "       extraSettingsList.style.display = 'none';\n"
     "       imgTag = advancedSettingsDiv.getElementsByTagName('img')[0];\n"
     "       imgTag.src = \"../images/add_sm.gif\";\n"
     "   } else {\n"
     "       extraSettingsList.style.display = 'block';\n"
     "       imgTag = advancedSettingsDiv.getElementsByTagName('img')[0];\n"
     "       imgTag.src = \"../images/remove_sm.gif\";\n"
     "   }\n"
     "});"
     );
 printf("</div>");
 
 printf("<div class=\"tabFooter\">");
 
 printf("<span class=\"small\">"
     "Contact <a href=\"../contacts.html\">us</A> to add a public hub."
     "</span>\n");
 printf("</div>");
 
 cgiMakeHiddenVar(hgHubConnectRemakeTrackHub, "on");
 
 puts("</FORM>");
 printf("</div>\n");
 
 cartWebEnd();
 }
 
 char *excludeVars[] = {"Submit", "submit", "hc_one_url", hgHubDoHubCheck,
     hgHubCheckUrl, hgHubDoClear, hgHubDoDisconnect,hgHubDoRedirect, hgHubDataText, 
     hgHubConnectRemakeTrackHub, NULL};
 
 int main(int argc, char *argv[])
 /* Process command line. */
 {
 long enteredMainTime = clock1000();
 oldVars = hashNew(10);
 cgiSpoof(&argc, argv);
 cartEmptyShell(doMiddle, hUserCookie(), excludeVars, oldVars);
 cgiExitTime("hgHubConnect", enteredMainTime);
 return 0;
 }