eb7e6ccb9b23c2dd5cd82de0237c6f25aecd2da1 max Thu Sep 17 06:02:11 2026 -0700 Hub tracks must not claim an undecorated name that a native track owns #Preview2 week - bugs introduced now will need a build patch to fix A hub track can be named on a URL, or found in the cart, without its "hub__" prefix, so that hub links stay readable. Nothing checked whether the assembly already had a track of that name, so when it did, the hub track took the variable: with the MethBase2 hub attached, setting the native hg38 Methbase track's visibility moved it to hub__Methbase and dropped it from the native track, which then did not draw. The two share the name "Methbase", and their subtracks share names too, so the same went for the _sel checkboxes. hubTrackOwnsBareName() now gates the seven places that fall back to the bare name - track and subtrack visibility, supertrack visibility, the two _hideKids variables, and the _sel checkbox. The bare name is the hub track's only where the assembly has no track of that name; otherwise it belongs to the native one, whose own pass over the track list consumes it. It answers from the track list loadFromTrackDb() has already built, so it costs a hash lookup: hub tracks are always prefixed in that list, so an undecorated name can only match a native track. A caller with no list to register (hgTrackUi) falls back to a single-row trackDb query per distinct bare name, cached. With 500 hub subtracks visible and 500 bare names on the URL - far past anything real - that is 189 queries either way, against 1189 for a query per name. Also clarifies the comments on hTrackDbWithCartVersion() and tdbForTrack(): the "this result is cached" note means the shared-memory trackDb cache only, which is off unless cacheTrackDbDir is set, and there is no memoizing besides it - so hTrackDbForTrack() per name reloads the whole trackDb each call. Measured at 57 seconds for 500 names before I abandoned that approach. refs #34246 diff --git src/hg/lib/hui.c src/hg/lib/hui.c index b02d6a8abce..54b082a5564 100644 --- src/hg/lib/hui.c +++ src/hg/lib/hui.c @@ -1,10965 +1,11059 @@ /* hui - human genome user interface common controls. */ /* 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 "cheapcgi.h" #include "htmshell.h" #include "jksql.h" #include "jsHelper.h" #include "sqlNum.h" #include "cart.h" #include "hdb.h" #include "hui.h" #include "hCommon.h" #include "hgConfig.h" #include "chainCart.h" #include "chainDb.h" #include "netCart.h" #include "obscure.h" #include "wiggle.h" #include "phyloTree.h" #include "hgMaf.h" #include "udc.h" #include "customTrack.h" #include "encode/encodePeak.h" #include "mdb.h" #include "web.h" #include "hPrint.h" #include "fileUi.h" #include "bigBed.h" #include "bigRmskUi.h" #include "bigWig.h" #include "regexHelper.h" #include "snakeUi.h" #include "vcfUi.h" #include "vcf.h" #include "errCatch.h" #include "samAlignment.h" #include "myVariants.h" #include "bedDetail.h" #include "pgSnp.h" #include "memgfx.h" #include "trackHub.h" #include "genark.h" #include "gtexUi.h" #include "genbank.h" #include "htmlPage.h" #include "longRange.h" #include "barChartUi.h" #include "interactUi.h" #include "interact.h" #include "hicUi.h" #include "bigDbSnp.h" #include "customComposite.h" #include "trackVersion.h" #include "hubConnect.h" #include "bigBedFilter.h" #include "bedMethyl.h" // TODO: these should go away after refactoring of multi-region link #include "hex.h" #include "net.h" #include "trashDir.h" #include #define SMALLBUF 256 #define MAX_SUBGROUP 9 #define ADD_BUTTON_LABEL "Show All" #define CLEAR_BUTTON_LABEL "Hide All" #define JBUFSIZE 2048 #define DEF_BUTTON "\"%s\"\n" #define DEF_BUTTON_JS "setCheckBoxesThatContain('%s',true,false,'%s','','%s');" \ "setCheckBoxesThatContain('%s',false,false,'%s','_defOff','%s');" #define DEFAULT_BUTTON(nameOrId,anc,beg,contains) \ printf(DEF_BUTTON,(anc),"defaults_sm.png","default"); \ safef(id, sizeof id, "btn_%s", (anc)); \ jsOnEventByIdF("click", id, DEF_BUTTON_JS,(nameOrId),(beg),(contains),(nameOrId),(beg),(contains)); #define PM_BUTTON "\"%s\"\n" #define PM_BUTTON_JS "setCheckBoxesThatContain('%s',%s,true,'%s','','%s');" #define PLUS_BUTTON(nameOrId,anc,beg,contains) \ printf(PM_BUTTON, (anc), "add_sm.gif", "+"); \ safef(id, sizeof id, "btn_%s", (anc)); \ jsOnEventByIdF("click", id, PM_BUTTON_JS, (nameOrId),"true", (beg),(contains)); #define MINUS_BUTTON(nameOrId,anc,beg,contains) \ printf(PM_BUTTON, (anc), "remove_sm.gif", "-"); \ safef(id, sizeof id, "btn_%s", (anc)); \ jsOnEventByIdF("click", id, PM_BUTTON_JS, (nameOrId),"false", (beg),(contains)); boolean isEncode2(char *database, char *track) /* Return true for tracks created by UCSC DCC during ENCODE production phase */ { if (startsWith("wgEncode", track)) return (sameString(database, "hg18") || sameString(database, "hg19") || sameString(database, "mm9")); return FALSE; } static char *htmlStringForDownloadsLink(char *database, struct trackDb *tdb, char *name,boolean nameIsFile) // Returns an HTML string for a downloads link { // If has fileSortOrder, then link to new hgFileUi if (!nameIsFile && trackDbSetting(tdb, FILE_SORT_ORDER) != NULL) { char * link = needMem(PATH_LEN); // 512 should be enough safef(link,PATH_LEN,"%s", hgFileUiName(),database, /*cartSessionVarName(),cartSessionId(cart),*/ tdb->track, name); // Note the hgsid would be needed if downloads page ever saved fileSortOrder to cart. return link; } else if (trackDbSetting(tdb, "wgEncode") != NULL && isEncode2(database, tdb->track)) // Downloads directory if this is ENCODE { const char *compositeDir = metadataFindValue(tdb, MDB_OBJ_TYPE_COMPOSITE); if (compositeDir == NULL && tdbIsComposite(tdb)) compositeDir = tdb->track; if (compositeDir != NULL) { struct dyString *dyLink = dyStringCreate("%s", hDownloadsServer(), database, ENCODE_DCC_DOWNLOADS, compositeDir, (nameIsFile?name:""), nameIsFile?"file":"files",name); return dyStringCannibalize(&dyLink); } } return NULL; } static boolean makeNamedDownloadsLink(char *database, struct trackDb *tdb,char *name) // Make a downloads link (if appropriate and then returns TRUE) { char *htmlString = htmlStringForDownloadsLink(database,trackDbTopLevelSelfOrParent(tdb),name,FALSE); if (htmlString == NULL) return FALSE; printf("%s", htmlString); freeMem(htmlString); return TRUE; } boolean makeDownloadsLink(char *database, struct trackDb *tdb) // Make a downloads link (if appropriate and then returns TRUE) { return makeNamedDownloadsLink(database, tdb,"Downloads"); } void makeTopLink(struct trackDb *tdb) // Link to top of UI page { if (trackDbSetting(tdb, "dimensions")) { char *upArrow = "⇑"; enum browserType browser = cgiBrowser(); if (browser == btIE || browser == btFF) upArrow = "↑"; // Note: the nested spans are so that javascript can determine position // and selectively display the link when appropriate printf("",upArrow); } } boolean makeSchemaLink(char *db,struct trackDb *tdb,char *label) // Make a table schema link (if appropriate and then returns TRUE) { #define SCHEMA_LINKED "%s" if (trackDataAccessible(db, tdb) && differentString("longTabix", tdb->type)) // FIXME: hgTables.showSchmaLongTabix is a currently a dummy routine, so let's not got here // until it's implemented { char *tbOff = trackDbSetting(tdb, "tableBrowser"); if (isNotEmpty(tbOff) && sameString(nextWord(&tbOff), "off")) { puts("(Download unavailable, see below)"); return FALSE; } char *hint = " title='Open data format (table schema) in new window'"; if (label == NULL) label = " View data format"; struct trackDb *topLevel = trackDbTopLevelSelfOrParent(tdb); printf(SCHEMA_LINKED, db, topLevel->grp, topLevel->track, tdb->table, hint, label); return TRUE; } return FALSE; } char *wgEncodeVocabLink(char *term,char *value,char *title, char *label,char *suffix) // returns allocated string of HTML link to controlled vocabulary term { #define VOCAB_LINK "%s" struct dyString *dyLink = NULL; char *encTerm = cgiEncode(term); char *encValue = cgiEncode(value); dyLink = dyStringCreate(VOCAB_LINK,encTerm,encValue,title,label); if (suffix != NULL) dyStringAppend(dyLink,suffix); // Don't encode since this may contain HTML freeMem(encTerm); freeMem(encValue); return dyStringCannibalize(&dyLink); } char *pairsAsHtmlTable( struct slPair *pairs, struct trackDb *tdb, boolean showLongLabel,boolean showShortLabel) /* Return a string which is an HTML table of the tags for this track. */ { if (pairs == NULL) return ""; struct dyString *dyTable = dyStringCreate(""); // the labels and the metadata pairs all come from trackDb, which a hub controls, escape if (showLongLabel) dyStringPrintf(dyTable,"", htmlEncode(tdb->longLabel)); if (showShortLabel) dyStringPrintf(dyTable,"" "",htmlEncode(tdb->shortLabel)); for(; pairs; pairs = pairs->next) { if (!sameString(pairs->name, "meta") && !isEmpty((char *)pairs->val)) dyStringPrintf(dyTable,"" "",htmlEncode(pairs->name), htmlEncode((char *)pairs->val)); } dyStringAppend(dyTable,"
%s
shortLabel:%s
%s:%s
"); return dyStringCannibalize(&dyTable); } char *metadataAsHtmlTable(char *db,struct trackDb *tdb,boolean showLongLabel,boolean showShortLabel) // If metadata from metaDb exists, return string of html with table definition { struct slPair *pairs = NULL; if ((pairs = trackDbMetaPairs(tdb)) != NULL) return pairsAsHtmlTable(pairs, tdb, showLongLabel, showShortLabel); const struct mdbObj *safeObj = metadataForTable(db,tdb,NULL); if (safeObj == NULL || safeObj->vars == NULL) return NULL; //struct dyString *dyTable = dyStringCreate("",tdb->table); struct dyString *dyTable = dyStringCreate("
"); if (showLongLabel) dyStringPrintf(dyTable,"",tdb->longLabel); if (showShortLabel) dyStringPrintf(dyTable,"" "",tdb->shortLabel); // Get the hash of mdb and cv term types struct hash *cvTermTypes = (struct hash *)cvTermTypeHash(); struct mdbObj *mdbObj = mdbObjClone(safeObj); // Important if we are going to remove vars! // Don't bother showing these mdbObjRemoveVars(mdbObj,MDB_OBJ_TYPE_COMPOSITE " " MDB_VAR_PROJECT " " MDB_OBJ_TYPE " " MDB_VAR_MD5SUM); mdbObjRemoveHiddenVars(mdbObj); mdbObjReorderByCv(mdbObj,FALSE);// Use cv defined order for visible vars struct mdbVar *mdbVar; for (mdbVar=mdbObj->vars;mdbVar!=NULL;mdbVar=mdbVar->next) { if ((sameString(mdbVar->var,MDB_VAR_FILENAME) || sameString(mdbVar->var,MDB_VAR_FILEINDEX) ) && trackDbSettingClosestToHome(tdb,MDB_VAL_ENCODE_PROJECT) != NULL) { dyStringPrintf(dyTable,"" ""); } else { // Don't bother with tableName if (cvTermTypes && differentString(mdbVar->var,MDB_VAR_TABLENAME)) { struct hash *cvTerm = hashFindVal(cvTermTypes,mdbVar->var); if (cvTerm != NULL) // even if cvTerm isn't used, { // it proves that it exists and a link is desirable if (!cvTermIsHidden(mdbVar->var)) { char *label = (char *)cvLabel(NULL,mdbVar->var); char *linkOfType = wgEncodeVocabLink(CV_TYPE,mdbVar->var,label, label,NULL); if (cvTermIsCvDefined(mdbVar->var)) { label = (char *)cvLabel(mdbVar->var,mdbVar->val); char *linkOfTerm = wgEncodeVocabLink(CV_TERM,mdbVar->val,label, label,NULL); dyStringPrintf(dyTable,"", linkOfType,linkOfTerm); freeMem(linkOfTerm); } else dyStringPrintf(dyTable,"", linkOfType,mdbVar->val); freeMem(linkOfType); continue; } } } dyStringPrintf(dyTable,"" "",mdbVar->var,mdbVar->val); } } dyStringAppend(dyTable,"
%s
shortLabel:%s
%s:",mdbVar->var); struct slName *fileSet = slNameListFromComma(mdbVar->val); while (fileSet != NULL) { struct slName *file = slPopHead(&fileSet); dyStringAppend(dyTable,htmlStringForDownloadsLink(db, tdb, file->name, TRUE)); if (fileSet != NULL) dyStringAppend(dyTable,"
"); slNameFree(&file); } dyStringAppend(dyTable,"
" "%s:%s
" "%s:%s
%s:%s
"); //mdbObjsFree(&mdbObj); // spill some memory return dyStringCannibalize(&dyTable); } boolean compositeMetadataToggle(char *db,struct trackDb *tdb,char *title, boolean embeddedInText,boolean showLongLabel) // If metadata from metaTbl exists, create a link that will allow toggling it's display { boolean hasMetaInHub = (trackDbSetting(tdb, "metaDb") != NULL) || (trackDbSetting(tdb, "metaTab") != NULL); if (!hasMetaInHub) { const struct mdbObj *safeObj = metadataForTable(db,tdb,NULL); if (safeObj == NULL || safeObj->vars == NULL) return FALSE; } char id[256]; safef(id, sizeof id, "div_%s_link", tdb->track); printf("%s%s", (embeddedInText?" ":"

"),id,tdb->track, (title?title:"")); jsOnEventByIdF("click", id, "return metadataShowHide(\"%s\",%s,true);", tdb->track, showLongLabel?"true":"false"); printf("

",tdb->track, metadataAsHtmlTable(db,tdb,showLongLabel,FALSE)); return TRUE; } /* Multi-region UI */ boolean makeMultiRegionLink(char *db, struct trackDb *tdb, struct cart *cart) /* Make a link to launch browser in multi-region custom URL mode, based on * track setting. This includes creating a custom track displaying the regions. * The link switches to exit multi-region if browser is already in multi-region mode * based on regions defined for this track. */ { char *regionUrl = trackDbSetting(tdb, "multiRegionsBedUrl"); if (isEmpty(regionUrl)) return FALSE; // make custom track for regions, alternating colors // TODO: truncate CT name and label at word boundary // TODO: fix bedPackDense to work with multi-region // TODO: limit number of regions ? struct dyString *dsCustomText = dyStringCreate( "track name=\'%s ROI\' description=\'[Regions of Interest] %s' " "visibility=dense bedPackDense=on labelOnFeature=on itemRgb=on noScoreFilter=on\n", tdb->shortLabel, tdb->longLabel); #ifdef LATER // TODO: libify struct dyString *ds = NULL; struct errCatch *errCatch = errCatchNew(); if (errCatchStart(errCatch)) { int sd = netUrlOpen(regionUrl); if (sd >= 0) { char *newUrl = NULL; int newSd = 0; if (netSkipHttpHeaderLinesHandlingRedirect(sd, regionUrl, &newSd, &newUrl)) { if (newUrl) /* redirect can modify the url */ { freeMem(newUrl); sd = newSd; } ds = netSlurpFile(sd); close(sd); } } } errCatchEnd(errCatch); if (errCatch->gotError) warn("%s", errCatch->message->string); // how come no warning if bad file ? errCatchFree(&errCatch); #endif // TODO: support $D, etc. in URL int sd = netUrlOpen(regionUrl); if (sd < 0) return FALSE; struct dyString *dsRegionBed = netSlurpFile(sd); close(sd); if (!dsRegionBed) return FALSE; char *regionBedTxt = dyStringCannibalize(&dsRegionBed); // count fields in BED. Accept up to BED9 (user-spec colors) char *bedTxt = cloneString(regionBedTxt); struct lineFile *lf = lineFileOnString(NULL, TRUE, bedTxt); char *words[9]; int bedSize = lineFileChopNext(lf, words, sizeof words); lineFileClose(&lf); freeMem(bedTxt); lf = lineFileOnString(NULL, TRUE, regionBedTxt); // TODO: refactor with interact multi-region static char *colorLight = "184,201,255"; // blue static char *colorDark = "0,0,0"; // black char *color = colorLight; boolean doLightColor = TRUE; int id = 1; char name[100]; char userColor[10]; struct bed *region; struct tempName mrTn; trashDirFile(&mrTn, "hgt", "custRgn_track", ".bed"); FILE *f = fopen(mrTn.forCgi, "w"); if (f == NULL) errAbort("can't create temp file %s", mrTn.forCgi); char regionInfo[1024]; char *regionFile = cloneString(mrTn.forCgi); // TODO: trackDb setting ? #define MULTI_REGION_BED_DEFAULT_PADDING 1000 int padding = MULTI_REGION_BED_DEFAULT_PADDING; safef(regionInfo, sizeof regionInfo, "#padding %d\n", padding); mustWrite(f, regionInfo, strlen(regionInfo)); #ifdef LATER safef(regionInfo, sizeof regionInfo, "#shortDesc %s\n", name); mustWrite(f, regionInfo, strlen(regionInfo)); #endif // write to trash file and custom track int regionCount = 0; while (lineFileChopNext(lf, words, bedSize)) { region = bedLoadN(words, bedSize); if (bedSize < 9) { // assign alternating light/dark color color = doLightColor ? colorLight : colorDark; doLightColor = !doLightColor; } else { struct rgbColor rgb = bedColorToRgb(region->itemRgb); safef(userColor, sizeof userColor, "%d,%d,%d", rgb.r, rgb.g, rgb.b); } if (bedSize < 4) { // region label based on chrom and an item number safef(name, sizeof name, "r%d/%s", id++, region->chrom); } else { strcpy(name, region->name); } // write to trash file safef(regionInfo, sizeof regionInfo, "%s\t%d\t%d\n", region->chrom, region->chromStart, region->chromEnd); mustWrite(f, regionInfo, strlen(regionInfo)); // write to custom track int start = max(region->chromStart - padding, 0); int end = min(region->chromEnd + padding, hChromSize(db, region->chrom)); dyStringPrintf(dsCustomText, "%s\t%d\t%d\t%s\t" "0\t.\t%d\t%d\t%s\n", region->chrom, start, end, name, start, end, color); regionCount++; } lineFileClose(&lf); fclose(f); // create SHA1 file; used to see if file has changed unsigned char hash[SHA_DIGEST_LENGTH]; SHA1((const unsigned char *)regionInfo, strlen(regionInfo), hash); char newSha1[(SHA_DIGEST_LENGTH + 1) * 2]; hexBinaryString(hash, SHA_DIGEST_LENGTH, newSha1, (SHA_DIGEST_LENGTH + 1) * 2); char sha1File[1024]; safef(sha1File, sizeof sha1File, "%s.sha1", mrTn.forCgi); f = mustOpen(sha1File, "w"); mustWrite(f, newSha1, strlen(newSha1)); carefulClose(&f); char customHtml[1000]; safef(customHtml, sizeof customHtml, "

Description

\n" "

This custom track displays regions of interest for the " "%s track.

", db, tdb->track, tdb->shortLabel); // TODO: support #padding in custom regions file enum trackVisibility vis = hTvFromString(cartUsualString(cart, tdb->track, hStringFromTv(tdb->visibility))); if (vis == tvHide) vis = tvDense; printf("

"); printf("" "Display regions of interest (%d)", MULTI_REGION_BED_WIN_FULL, tdb->track, cgiEncode(regionFile), tdb->track, hStringFromTv(vis), CT_CUSTOM_DOC_TEXT_VAR, cgiEncode(customHtml), CT_CUSTOM_TEXT_VAR, cgiEncode(dyStringCannibalize(&dsCustomText)), regionCount); printf(" in multi-region view (custom regions mode)"); printf("   "); printf("(Help)\n"); printf("

"); return TRUE; } static void printDownloadUrl(char *downloadUrl, char *database, char *track) /* given a string