e25b1163ff40d3eacbd7115f0074353a841f9b18
chmalee
Mon Mar 7 12:58:51 2022 -0800
hub developer mode was already on by default but was not controlled via hg.conf. Leave it on by default but allow it to be turned off via hg.conf, and clarify that in the documentation, refs #28831
diff --git src/hg/hgHubConnect/hgHubConnect.c src/hg/hgHubConnect/hgHubConnect.c
index fcb25b5..7f7ffb1 100644
--- src/hg/hgHubConnect/hgHubConnect.c
+++ src/hg/hgHubConnect/hgHubConnect.c
@@ -1,1678 +1,1680 @@
/* 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 kent/LICENSE or http://genome.ucsc.edu/license/ 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"
#include "pipeline.h"
#include "hubPublic.h"
static boolean measureTiming;
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;
};
static void ourCellStart()
{
fputs("
", stdout); // do not add a newline
}
static void ourCellEnd()
{
puts("
");
}
static void ourPrintCellLink(char *str, char *url)
{
ourCellStart();
printf("\n", url);
if (str != NULL)
fputs(str, stdout); // do not add a newline -- was causing trailing blanks get copied in cut and paste
puts("");
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,"%s" , 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,"%s" , 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,
"[+] %s..."
"[-] %s"
, 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
puts("
\n"
"
");
printf(""); // return from within DIV and FROM is probably not a good idea
puts("
"); // tabSection and .unlistedHubs
return;
}
// time to output the big table. First the header
puts(
"
"
"
Display
"
"
Hub Name
"
"
Description
"
"
AssembliesClick to connect and browse directly
"
"
\n"
"\n");
// start first row
printf("");
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("
");
ourCellStart();
safef(id, sizeof id, "hubDisconnectButtonU%d", count);
printf("\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("ERROR: %s "
"Debug Help\n",
hub->errorMessage);
safef(id, sizeof id, "hubClearButton%d", count);
// give people a chance to clear the error
printf(""
, 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("
");
}
printf("\n");
puts("");
printf(""); // .tabSection
printf(""); // #unlistedHubs
}
int doValidateNewHub(char *hubUrl)
/* Run hubCheck on a hub. */
{
int ret = 0;
udcSetCacheTimeout(1);
char *expireTime = cfgOptionDefault("browser.cgiExpireMinutes", "20");
unsigned expireMinutes = sqlUnsigned(expireTime);
int hubCheckTimeout = (expireMinutes - 1) * 60 > 0 ? (expireMinutes - 1) * 60 : 60;
printf("");
char *cmd[] = {"loader/hubCheck", "-htmlOut", "-noTracks", hubUrl, NULL};
struct errCatch *errCatch = errCatchNew();
if (errCatchStart(errCatch))
{
struct pipeline *pl = pipelineOpen1(cmd, pipelineRead | pipelineNoAbort, NULL, NULL, hubCheckTimeout);
struct lineFile *lf = pipelineLineFile(pl);
char *line;
while (lineFileNext(lf, &line, NULL))
jsInlineF("%s", line);
pipelineClose(&pl);
// the 'false' below prevents a few hub-search specific jstree configuration options
jsInline("hubSearchTree.init(false);");
}
errCatchEnd(errCatch);
if (errCatch->gotError || errCatch->gotWarning)
{
printf("hubCheck timed out after running for %d minute%s. Please try on a Unix command line", hubCheckTimeout / 60, hubCheckTimeout/60 > 1 ? "s" : "");
ret = 1;
}
errCatchFree(&errCatch);
return ret;
}
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");
// the outer div for all the elements in the tab
puts("
\n"
" You may also contact us if you have any "
"issues or questions on hub development.");
puts("
"); // .tabSection
puts("
");
puts("
Check a hub for errors
");
printf("");
printf("\n", hubUrlVal);
printf("\n");
printf("  Load Example URL\n");
puts("
Use the URL bar above to check a hub for errors. This will "
"validate the hub's configuration files, including hub.txt, "
"genomes.txt and trackDb.txt. "
"It will also present a hierarchical tree of tracks with any errors in red. A hub "
"with no errors still shows the tree which can be used to explore the track hierarchy. "
"Hub error checking will always refresh the files and never use our remote file cache (see below)."
"
\n "
);
puts("
"); // .tabSection
puts("
");
puts("
Enable Genome Browser debugging modes
");
puts("
These apply to all connected hubs. By default, caching is activated and track load times are not shown, but you can change these settings while developing your hub:
");
puts("
");
puts("
"); // margin-left
puts("
"); // tabSection
puts("
"); // #hubDeveloper
jsOnEventById("click", "hubValidateButton", "makeIframe(event)");
}
void printSearchAndFilterBoxes(int searchEnabled, char *hubSearchTerms, char *dbFilter)
/* Create the text boxes for search and database filtering along with the required
* javscript */
{
printf("");
}
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 restricted by search terms: %s\n", hubSearchTerms);
puts("\n");
jsOnEventById("click", "hubDeleteSearchButton",
"document.searchHubForm.elements['hubSearchTerms'].value='';"
"document.searchHubForm.elements['hubDbFilter'].value='';"
"document.searchHubForm.submit();return true;");
puts("
\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("
\n");
}
void printHubListHeader()
/* Write out the header for a list of hubs in its own table */
{
puts("
"
"
"
"
Display
"
"
Hub Name
"
"
Description
"
//"
Assemblies
"
"
AssembliesClick to connect and browse directly
"
"
");
}
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("
\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("\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("\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("ERROR: %s "
"Debug Help",
hubInfo->errorMessage);
safef(jsId, sizeof jsId, "hubClearButton%d", count);
printf(
""
, 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("
\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, " Metadata: %s",
htmlEncode(dyStringContents(child->metaTags)));
}
if (isNotEmpty(dyStringContents(child->descriptionMatch)))
{
dyStringPrintf(dy, " Description: %s",
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, " Metadata: %s",
htmlEncode(dyStringContents(tdbOut->metaTags)));
}
if (isNotEmpty(dyStringContents(tdbOut->descriptionMatch)))
{
dyStringPrintf(dy, " Description: %s",
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: "
"%s'},",
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, " %s",
htmlEncode(dyStringContents(genomeOut->metaTags)));
}
if (isNotEmpty(dyStringContents(genomeOut->descriptionMatch)))
{
dyStringPrintf(dy, " Assembly Description: %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
containing details
* about the search results. */
{
if (hubSearchResult != NULL)
printf("
\n");
outputPublicTableRow(hubInfo, count);
if (hubSearchResult != NULL)
{
printf("
\n");
struct trackHub *hub = fetchTrackHub(hubInfo);
if (hub != NULL)
{
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
printf("
\n");
printf("", hubInfo->id); // div for the jstree for this hub's search result(s)
printf("
\n");
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)
*/
{
printf("