6a0a4de569da7ed5c63d2781badccdd5249149a1 braney Thu Nov 24 09:16:29 2022 -0800 gracefully accept trackDbs that have superTrack children in different groups. diff --git src/hg/hgTracks/searchTracks.c src/hg/hgTracks/searchTracks.c index d0a633d..f6b3441 100644 --- src/hg/hgTracks/searchTracks.c +++ src/hg/hgTracks/searchTracks.c @@ -1,1244 +1,1246 @@ /* Track search code used by hgTracks CGI */ /* Copyright (C) 2012 The Regents of the University of California * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */ #include <pthread.h> #include "common.h" #include "search.h" #include "hCommon.h" #include "memalloc.h" #include "obscure.h" #include "dystring.h" #include "hash.h" #include "cheapcgi.h" #include "hPrint.h" #include "htmshell.h" #include "cart.h" #include "hgTracks.h" #include "web.h" #include "jksql.h" #include "hdb.h" #include "mdb.h" #include "fileUi.h" #include "trix.h" #include "jsHelper.h" #include "imageV2.h" #include "hgConfig.h" #include "trackHub.h" #include "hubConnect.h" #include "hubPublic.h" #include "hubSearchText.h" #include "errCatch.h" #define TRACK_SEARCH_FORM "trackSearch" #define SEARCH_RESULTS_FORM "searchResults" #define TRACK_SEARCH_CURRENT_TAB "tsCurTab" #define TRACK_SEARCH_SIMPLE "tsSimple" #define TRACK_SEARCH_ON_NAME "tsName" #define TRACK_SEARCH_ON_TYPE "tsType" #define TRACK_SEARCH_ON_GROUP "tsGroup" #define TRACK_SEARCH_ON_DESCR "tsDescr" #define TRACK_SEARCH_SORT "tsSort" #define TRACK_SEARCH_ON_HUBS "tsIncludePublicHubs" // the list of found tracks struct slRef *tracks = NULL; // for advanced search only: // associates hub id's to hub urls that have search results, // used to get huburls onto hgTrackUi links struct hash *hubIdsToUrls = NULL; static int gCmpGroup(const void *va, const void *vb) /* Compare groups based on label. */ { const struct group *a = *((struct group **)va); const struct group *b = *((struct group **)vb); return strcmp(a->label, b->label); } // Would like to do a radio button choice ofsorts enum sortBy { sbRelevance=0, sbAbc =1, sbHierarchy=2, }; static int gCmpTrackHierarchy(const void *va, const void *vb) /* Compare tracks based on longLabel. */ { const struct slRef *aa = *((struct slRef **)va); const struct slRef *bb = *((struct slRef **)vb); const struct track *a = ((struct track *) aa->val); const struct track *b = ((struct track *) bb->val); if ( tdbIsFolder(a->tdb) && !tdbIsFolder(b->tdb)) return -1; else if (!tdbIsFolder(a->tdb) && tdbIsFolder(b->tdb)) return 1; if ( tdbIsContainer(a->tdb) && !tdbIsContainer(b->tdb)) return -1; else if (!tdbIsContainer(a->tdb) && tdbIsContainer(b->tdb)) return 1; if (!tdbIsContainerChild(a->tdb) && tdbIsContainerChild(b->tdb)) return -1; else if ( tdbIsContainerChild(a->tdb) && !tdbIsContainerChild(b->tdb)) return 1; return strcasecmp(a->longLabel, b->longLabel); } static int gCmpTrack(const void *va, const void *vb) /* Compare tracks based on longLabel. */ { const struct slRef *aa = *((struct slRef **)va); const struct slRef *bb = *((struct slRef **)vb); const struct track *a = ((struct track *) aa->val); const struct track *b = ((struct track *) bb->val); return strcasecmp(a->longLabel, b->longLabel); } static void findTracksSort(struct slRef **pTrack, enum sortBy sortBy) { if (sortBy == sbHierarchy) slSort(pTrack, gCmpTrackHierarchy); else if (sortBy == sbAbc) slSort(pTrack, gCmpTrack); else slReverse(pTrack); } static int getFormatTypes(char ***pLabels, char ***pTypes) { char *crudeTypes[] = { ANYLABEL, "bam", "psl", "chain", "netAlign", "maf", "bed", "bigBed", "ctgPos", "expRatio", "genePred", "broadPeak", "narrowPeak", "rmsk", "bedGraph", "bigWig", "wig", "wigMaf" }; // Non-standard: // type altGraphX // type axt // type bed5FloatScore // type bed5FloatScoreWithFdr // type chromGraph // type clonePos // type coloredExon // type encodeFiveC // type factorSource // type ld2 // type logo // type maf // type sample // type wigMafProt 0.0 1.0 char *nicerTypes[] = { ANYLABEL, "Alignment binary (bam) - binary SAM", "Alignment Blast (psl) - Blast output", "Alignment Chains (chain) - Pairwise alignment", "Alignment Nets (netAlign) - Net alignments", "Alignments (maf) - multiple alignment format", "bed - browser extensible data", "bigBed - self index, often remote bed format", "ctgPos - Contigs", "expRatio - Expression ratios", "Genes (genePred) - Gene prediction and annotation", "Peaks Broad (broadPeak) - ENCODE large region peak format", "Peaks Narrow (narrowPeak) - ENCODE small region peak format", "Repeats (rmsk) - Repeat masking", "Signal (bedGraph) - graphically represented bed data", "Signal (bigWig) - self index, often remote wiggle format", "Signal (wig) - wiggle format", "Signal (wigMaf) - multiple alignment wiggle" }; int ix = 0, count = sizeof(crudeTypes)/sizeof(char *); char **labels; char **values; AllocArray(labels, count); AllocArray(values, count); for (ix=0;ix<count;ix++) { labels[ix] = cloneString(nicerTypes[ix]); values[ix] = cloneString(crudeTypes[ix]); } *pLabels = labels; *pTypes = values; return count; } static struct trackDb *getSuperTrackTdbs(struct trackDb *tdbList) /* Supertracks are not in the main trackDbList returned by hubAddTracks, find them * here since our search hits may hit them */ { struct hash *superTrackHash = hashNew(0); struct trackDb *tdb, *ret = NULL; for (tdb = tdbList; tdb != NULL; tdb = tdb->next) { if (tdbIsSuperTrackChild(tdb) && hashFindVal(superTrackHash, tdb->parent->track) == NULL) { hashAdd(superTrackHash, tdb->parent->track, tdb->parent); } } struct hashEl *hel, *helList = hashElListHash(superTrackHash); for (hel = helList; hel != NULL; hel = hel->next) slAddHead(&ret, (struct trackDb *)hel->val); return ret; } static void hubTdbListToTrackList(struct trackDb *tdbList, struct track **trackList, struct slName *trackNames) /* Recursively convert a (struct trackDb *) to a (struct track *) */ { struct trackDb *tmp, *next; for (tmp = tdbList; tmp != NULL; tmp = next) { next = tmp->next; if (slNameInList(trackNames, tmp->track)) { struct track *t = trackFromTrackDb(tmp); slAddHead(trackList, t); } if (tmp->subtracks) hubTdbListToTrackList(tmp->subtracks, trackList, trackNames); } } static void hubTdbListAddSupers(struct trackDb *tdbList, struct track **trackList, struct slName *trackNames) /* a track we are looking for might be a super track and thus not in tdbList, look for it here */ { struct trackDb *tmp, *superTrackDbs = getSuperTrackTdbs(tdbList); for (tmp = superTrackDbs; tmp != NULL; tmp = tmp->next) { if (slNameInList(trackNames, tmp->track)) { struct track *tg = trackFromTrackDb(tmp); slAddHead(trackList, tg); } } } struct hubSearchTracks /* A helper struct for collapsing a (struct hubSearchText *) into just the parts * we need for looking up the track hits */ { struct hubSearchTracks *next; char *hubUrl; // the url to this hub which is used as a key into the search hash int hubId; struct hubConnectStatus *hub; // the hubStatus result struct slName *searchedTracks; // the track names the search terms matched against }; struct paraFetchData /* A helper struct for managing connecting to many hubs in parallel and adding the * relevant tracks to the global (struct slRef *)tracks struct. */ { struct paraFetchData *next; char *hubName; // the name of the hub for measureTiming results struct hubSearchTracks *hst; // the tracks we are adding to the search results struct track *tlist; // the resulting tracks to add to the global trackList pthread_t *threadId; // so we can stop the thread if it has been taking too long long searchTime; // how many milliseconds did it take to search this hub boolean done; }; // helper variables for connecting to hubs in parallel pthread_mutex_t pfdMutex = PTHREAD_MUTEX_INITIALIZER; struct paraFetchData *pfdListInitial = NULL; struct paraFetchData *pfdList = NULL; struct paraFetchData *pfdRunning = NULL; struct paraFetchData *pfdDone = NULL; void *addUnconnectedHubSearchResults(void *threadParam) /* Add a not yet connected hub to the search results */ { pthread_t *thread = threadParam; struct paraFetchData *pfd = NULL; // this thread will just happily keep working until waitForSearchResults() finishes, // moving it's completed work onto pfdDone, so we can safely detach pthread_detach(*thread); boolean allDone = FALSE; while(1) { pthread_mutex_lock(&pfdMutex); // the wait function will set pfdList = NULL, so don't start up any more // stuff if that happens: if (!pfdList) allDone = TRUE; else { pfd = slPopHead(&pfdList); pfd->threadId = threadParam; slAddHead(&pfdRunning, pfd); } pthread_mutex_unlock(&pfdMutex); if (allDone) return NULL; struct hubSearchTracks *hst = pfd->hst; struct track *tracksToAdd = NULL; long startTime = clock1000(); struct errCatch *errCatch = errCatchNew(); if (errCatchStart(errCatch)) { pfd->done = FALSE; struct trackDb *tdbList = hubAddTracks(hst->hub, database); if (measureTiming) measureTime("After connecting to hub %s: '%d': ", hst->hubUrl, hst->hubId); // get composite and subtracks into trackList hubTdbListToTrackList(tdbList, &tracksToAdd, hst->searchedTracks); hubTdbListAddSupers(tdbList, &tracksToAdd, hst->searchedTracks); pfd->done = TRUE; pfd->tlist = tracksToAdd; pfd->searchTime = clock1000() - startTime;; } errCatchEnd(errCatch); pthread_mutex_lock(&pfdMutex); slRemoveEl(&pfdRunning, pfd); slAddHead(&pfdDone, pfd); if (measureTiming) measureTime("Finished finding tracks for hub '%s': ", pfd->hubName); pthread_mutex_unlock(&pfdMutex); } //always return NULL for pthread_create() return NULL; } static void hubSearchHashToPfdList(char *descSearch, struct hash *searchResultsHash, struct hash *hubLookup, struct sqlConnection *conn) /* getHubSearchResults() returned a hash of search hits to various hubs, convert that * into something we can work on in parallel */ { struct hubSearchTracks *ret = NULL; struct hashCookie cookie = hashFirst(searchResultsHash); struct hash *hubUrlsToTrackList = hashNew(0); struct hashEl *hel = NULL; struct dyString *trackName = dyStringNew(0); struct slName *connectedHubs = hubConnectHubsInCart(cart); while ((hel = hashNext(&cookie)) != NULL) { struct hubSearchText *hst = (struct hubSearchText *)hel->val; struct hubEntry *hubInfo = (struct hubEntry *) hashFindVal(hubLookup, hst->hubUrl); if (isNotEmpty(hubInfo->errorMessage)) continue; // if we were already connected to this hub, then it's search hits // were already taken care of by the regular search code char hubId[256]; safef(hubId, sizeof(hubId), "%d", hubInfo->id); if (slNameInList(connectedHubs, hubId)) continue; struct hubConnectStatus *hub = hubConnectStatusForId(conn, hubInfo->id); // the hubSearchText contains multiple entries per lookup in order to form // the hgHubConnect UI. We only need one type of hit here: struct hubSearchTracks *found = hashFindVal(hubUrlsToTrackList, hst->hubUrl); for (; hst != NULL; hst = hst->next) { // don't add results for matches to the hub descriptionUrl, only to track names/descs if (isNotEmpty(hst->track) && hst->textLength != hubSearchTextMeta) { // hst->textLength=hubSearchTextLong denotes hits to track description if (isNotEmpty(descSearch) && hst->textLength != hubSearchTextLong) continue; if (!found) { AllocVar(found); found->hubUrl = hst->hubUrl; found->searchedTracks = NULL; found->hub = hub; found->hubId = hubInfo->id; slAddHead(&ret, found); hashAdd(hubUrlsToTrackList, hst->hubUrl, found); hashAdd(hubIdsToUrls, hubId, hst->hubUrl); } dyStringPrintf(trackName, "%s%d_%s", hubTrackPrefix, hubInfo->id, hst->track); slNameStore(&found->searchedTracks, cloneString(trackName->string)); dyStringClear(trackName); } } } struct hubSearchTracks *t; for (t = ret; t != NULL; t = t->next) { struct paraFetchData *pfd; AllocVar(pfd); pfd->hubName = t->hubUrl; pfd->hst = t; slAddHead(&pfdList, pfd); slAddHead(&pfdListInitial, CloneVar(pfd)); } if (measureTiming) measureTime("Finished converting hubSearchText to hubSearchTracks and pfd"); } void waitForSearchResults(int numThreads, pthread_t *threadList) /* Run each thread and kill the ones that take too long */ { // only wait 5 seconds, if something is in the cache we can show it // otherwise just ignore, nobody should wait more than 5 seconds // for a simple track search. Although note that just getting to // this point can take quite a while depending on hgcentral // connections and obtaining trackDb. int maxTime = 5 * 1000; int waitTime = 0; int lockStatus = 0; struct paraFetchData *pfd; while(1) { sleep1000(50); waitTime += 50; boolean allDone = TRUE; // we don't want to block in the event one of the child threads is // taking forever lockStatus = pthread_mutex_trylock(&pfdMutex); if (pfdList || pfdRunning) allDone = FALSE; if (allDone) { if (lockStatus == 0) // we aquired the lock pthread_mutex_unlock(&pfdMutex); break; } if (waitTime >= maxTime) { if (lockStatus == 0) // we aquired the lock pthread_mutex_unlock(&pfdMutex); break; } if (lockStatus == 0) // release the lock if we got it pthread_mutex_unlock(&pfdMutex); } // now that we've waited the maximum time we need to kill // any running threads and add the results of any threads // that ran successfully lockStatus = pthread_mutex_trylock(&pfdMutex); struct paraFetchData *neverRan = pfdList; if (lockStatus == 0) { // prevent still running threads from continuing pfdList = NULL; if (measureTiming) fprintf(stdout, "<span class='timing'>Successfully aquired lock, adding any succesful thread data\n<br></span>"); for (pfd = pfdDone; pfd != NULL; pfd = pfd->next) { struct track *t; for (t = pfd->tlist; t != NULL; t = t->next) refAdd(&tracks, t); if (measureTiming) measureTime("'%s' search times", pfd->hubName); } for (pfd = pfdRunning; pfd != NULL; pfd = pfd->next) { pthread_cancel(*pfd->threadId); if (measureTiming) measureTime("'%s' search times: timed out", pfd->hubName); } for (pfd = neverRan; pfd != NULL; pfd = pfd->next) if (measureTiming) measureTime("'%s' search times: never ran", pfd->hubName); } else { // Should we warn or something that results are still waiting? As of now // just silently return instead, and note that no unconnected hub data // will show up (we get connected hub results for free because of // trackDbCaching) if (measureTiming) measureTime("Timed out searching hubs"); } if (lockStatus == 0) pthread_mutex_unlock(&pfdMutex); } void addHubSearchResults(struct slName *nameList, char *descSearch) /* add public hubs to the track list */ { struct sqlConnection *conn = hConnectCentral(); char *hubSearchTableName = hubSearchTextTableName(); char *publicTable = cfgOptionEnvDefault("HGDB_HUB_PUBLIC_TABLE", hubPublicTableConfVariable, defaultHubPublicTableName); char *statusTable = cfgOptionEnvDefault("HGDB_HUB_STATUS_TABLE", hubStatusTableConfVariable, defaultHubStatusTableName); struct dyString *extra = dyStringNew(0); if (nameList) { struct slName *tmp = NULL; for (tmp = nameList; tmp != NULL; tmp = tmp->next) { sqlDyStringPrintf(extra, "label like '%%%s%%'", tmp->name); if (tmp->next) sqlDyStringPrintf(extra, " and "); } } if (sqlTableExists(conn, hubSearchTableName)) { struct hash *searchResultsHash = hashNew(0); struct hash *pHash = hashNew(0); struct slName *hubsToPrint = NULL; addPublicHubsToHubStatus(cart, conn, publicTable, statusTable); struct hash *hubLookup = buildPublicLookupHash(conn, publicTable, statusTable, &pHash); char *db = cloneString(trackHubSkipHubName(database)); tolowers(db); getHubSearchResults(conn, hubSearchTableName, descSearch, isNotEmpty(descSearch), db, hubLookup, &searchResultsHash, &hubsToPrint, dyStringCannibalize(&extra)); hubSearchHashToPfdList(descSearch, searchResultsHash, hubLookup, conn); if (measureTiming) measureTime("after querying hubSearchText table and ready to start threads"); int ptMax = atoi(cfgOptionDefault("parallelFetch.threads", "20")); int pfdListCount = 0, pt; if (ptMax > 0) { pfdListCount = slCount(pfdList); pthread_t *threads = NULL; ptMax = min(ptMax, pfdListCount); if (ptMax > 0) { AllocArray(threads, ptMax); for (pt = 0; pt < ptMax; pt++) { int rc = pthread_create(&threads[pt], NULL, addUnconnectedHubSearchResults, &threads[pt]); if (rc ) errAbort("Unexpected error in pthread_create"); } } waitForSearchResults(ptMax, threads); } } if (measureTiming) measureTime("Total time spent searching hubs"); } static void simpleSearchForTracks(char *simpleEntry) // Performs the simple search and returns the found tracks. { // Prepare for trix search if (!isEmpty(simpleEntry)) { int trixWordCount = 0; char *tmp = cloneString(simpleEntry); char *val = nextWord(&tmp); struct slName *el, *trixList = NULL; while (val != NULL) { slNameAddTail(&trixList, val); trixWordCount++; val = nextWord(&tmp); } if (trixWordCount > 0 && !isHubTrack(database)) { // Unfortunately trixSearch can't handle the slName list int i; char **trixWords = needMem(sizeof(char *) * trixWordCount); for (i = 0, el = trixList; el != NULL; i++, el = el->next) trixWords[i] = strLower(el->name); // Now open the trix file char trixFile[HDB_MAX_PATH_STRING]; getSearchTrixFile(database, trixFile, sizeof(trixFile)); struct trix *trix = trixOpen(trixFile); struct trixSearchResult *tsList = trixSearch(trix, trixWordCount, trixWords, tsmExpand); for ( ; tsList != NULL; tsList = tsList->next) { struct track *track = (struct track *) hashFindVal(trackHash, tsList->itemId); if (track != NULL) // It is expected that this is NULL { // (e.g. when trix references trackDb tracks which have no tables) refAdd(&tracks, track); } } //trixClose(trix); // don't bother (this is a CGI that is about to end) } } } static void advancedSearchForTracks(struct sqlConnection *conn,struct group *groupList, char *nameSearch, char *typeSearch, char *descSearch, char *groupSearch, struct slPair *mdbPairs, boolean includeHubResults) // Performs the advanced search and returns the found tracks. { int tracksFound = 0; int numMetadataNonEmpty = 0; struct slPair *pair = mdbPairs; for (; pair!= NULL;pair=pair->next) { if (!isEmpty((char *)(pair->val))) numMetadataNonEmpty++; } if (!isEmpty(groupSearch) && sameString(groupSearch,ANYLABEL)) groupSearch = NULL; if (!isEmpty(typeSearch) && sameString(typeSearch,ANYLABEL)) typeSearch = NULL; if (isEmpty(nameSearch) && isEmpty(typeSearch) && isEmpty(descSearch) && isEmpty(groupSearch) && numMetadataNonEmpty == 0) return; // First do the metaDb searches, which can be done quickly for all tracks with db queries. struct hash *matchingTracks = NULL; if (numMetadataNonEmpty) { struct mdbObj *mdbObj, *mdbObjs = mdbObjRepeatedSearch(conn,mdbPairs,TRUE,FALSE); if (mdbObjs) { for (mdbObj = mdbObjs; mdbObj != NULL; mdbObj = mdbObj->next) { if (matchingTracks == NULL) matchingTracks = newHash(0); hashAddInt(matchingTracks, mdbObj->obj, 1); } mdbObjsFree(&mdbObjs); } if (matchingTracks == NULL) return; } // Set the word lists up once struct slName *nameList = NULL; if (!isEmpty(nameSearch)) nameList = slNameListOfUniqueWords(cloneString(nameSearch),TRUE); // TRUE means respect quotes struct slName *descList = NULL; if (!isEmpty(descSearch)) descList = slNameListOfUniqueWords(cloneString(descSearch),TRUE); struct group *group; for (group = groupList; group != NULL; group = group->next) { if (isEmpty(groupSearch) || sameString(group->name, groupSearch)) { if (group->trackList == NULL) continue; struct trackRef *tr; for (tr = group->trackList; tr != NULL; tr = tr->next) { struct track *track = tr->track; char *trackType = cloneFirstWord(track->tdb->type); // will be spilled if ((matchingTracks == NULL || hashLookup(matchingTracks, track->track) != NULL) && ( isEmpty(typeSearch) || (sameWord(typeSearch, trackType) && !tdbIsComposite(track->tdb))) && (isEmpty(nameSearch) || searchNameMatches(track->tdb, nameList)) && (isEmpty(descSearch) || searchDescriptionMatches(track->tdb, descList))) { if (track != NULL) { tracksFound++; refAdd(&tracks, track); } else warn("found group track is NULL."); } if (track->subtracks != NULL) { struct track *subTrack; for (subTrack = track->subtracks; subTrack != NULL; subTrack = subTrack->next) { trackType = cloneFirstWord(subTrack->tdb->type); // will be spilled if ( (matchingTracks == NULL || hashLookup(matchingTracks, subTrack->track) != NULL) && (isEmpty(typeSearch) || sameWord(typeSearch, trackType)) && (isEmpty(nameSearch) || searchNameMatches(subTrack->tdb, nameList)) && (isEmpty(descSearch) // subtracks inherit description || searchDescriptionMatches(subTrack->tdb, descList) || (tdbIsCompositeChild(subTrack->tdb) && subTrack->parent && searchDescriptionMatches(subTrack->parent->tdb, descList)))) { if (track != NULL) { tracksFound++; refAdd(&tracks, subTrack); } else warn("found subtrack is NULL."); } } } } } } if (measureTiming) measureTime("searched native tracks: "); if (includeHubResults) addHubSearchResults(nameList,descSearch); } #define MAX_FOUND_TRACKS 100 static void findTracksPageLinks(int tracksFound, int startFrom, int instance) { char id[256]; if (tracksFound <= MAX_FOUND_TRACKS) return; // Opener int willStartAt = 0; int curPage = (startFrom/MAX_FOUND_TRACKS) + 1; int endAt = startFrom+MAX_FOUND_TRACKS; if (endAt > tracksFound) endAt = tracksFound; hPrintf("<span><em style='font-size:.9em;'>Listing %d - %d of %d tracks</em> ", startFrom+1,endAt,tracksFound); // << and < if (startFrom >= MAX_FOUND_TRACKS) { safef(id, sizeof id, "ftpl%d-first", instance); hPrintf("<a href='../cgi-bin/hgTracks?%s=Search&%s=0' id='%s' title='First page of found tracks'" ">«</a> ", TRACK_SEARCH,TRACK_SEARCH_PAGER,id); jsOnEventByIdF("click", id, "return findTracks.page(\"%s\",0);", TRACK_SEARCH_PAGER); safef(id, sizeof id, "ftpl%d-prev", instance); willStartAt = startFrom - MAX_FOUND_TRACKS; hPrintf(" <a href='../cgi-bin/hgTracks?%s=Search&%s=%d' id='%s' " "title='Previous page of found tracks'>‹</a> ", TRACK_SEARCH,TRACK_SEARCH_PAGER,willStartAt,id); jsOnEventByIdF("click", id, "return findTracks.page(\"%s\",%d);", TRACK_SEARCH_PAGER,willStartAt); } // page number links int lastPage = (tracksFound/MAX_FOUND_TRACKS); if ((tracksFound % MAX_FOUND_TRACKS) > 0) lastPage++; int thisPage = curPage - 3; // Window of 3 pages above and below if (thisPage < 1) thisPage = 1; for (;thisPage <= lastPage && thisPage <= curPage + 3; thisPage++) { safef(id, sizeof id, "ftpl%d-%d", instance, thisPage); if (thisPage != curPage) { willStartAt = ((thisPage - 1) * MAX_FOUND_TRACKS); endAt = willStartAt+ MAX_FOUND_TRACKS; if (endAt > tracksFound) endAt = tracksFound; hPrintf(" <a href='../cgi-bin/hgTracks?%s=Search&%s=%d' id='%s' " "title='Page %d (%d - %d) tracks'>%d</a> ", TRACK_SEARCH,TRACK_SEARCH_PAGER,willStartAt,id,thisPage,willStartAt+1,endAt,thisPage); jsOnEventByIdF("click", id, "return findTracks.page(\"%s\",%d);",TRACK_SEARCH_PAGER,willStartAt); } else hPrintf(" <em style='color:%s;'>%d</em> ",COLOR_DARKGREY,thisPage); } // > and >> if ((startFrom + MAX_FOUND_TRACKS) < tracksFound) { safef(id, sizeof id, "ftpl%d-next", instance); willStartAt = startFrom + MAX_FOUND_TRACKS; hPrintf(" <a href='../cgi-bin/hgTracks?%s=Search&%s=%d' id='%s' " "title='Next page of found tracks'>›</a> ", TRACK_SEARCH,TRACK_SEARCH_PAGER,willStartAt,id); jsOnEventByIdF("click", id, "return findTracks.page(\"%s\",%d);",TRACK_SEARCH_PAGER,willStartAt); safef(id, sizeof id, "ftpl%d-last", instance); willStartAt = tracksFound - (tracksFound % MAX_FOUND_TRACKS); if (willStartAt == tracksFound) willStartAt -= MAX_FOUND_TRACKS; hPrintf(" <a href='../cgi-bin/hgTracks?%s=Search&%s=%d' id='%s' title='Last page of found tracks' " ">»</a></span>\n", TRACK_SEARCH,TRACK_SEARCH_PAGER,willStartAt,id); jsOnEventByIdF("click", id, "return findTracks.page(\"%s\",%d);",TRACK_SEARCH_PAGER,willStartAt); } } static void displayFoundTracks(struct cart *cart, struct slRef *tracks, int tracksFound, enum sortBy sortBy) // Routine for displaying found tracks { char id[256]; char javascript[1024]; hPrintf("<div id='found' style='display:none;'>\n"); // This div is emptied with 'clear' button if (tracksFound < 1) { hPrintf("<p>No tracks found</p>\n"); } else { hPrintf("<form action='%s' name='%s' id='%s' method='post'>\n\n", hgTracksName(),SEARCH_RESULTS_FORM,SEARCH_RESULTS_FORM); cartSaveSession(cart); // Creates hidden var of hgsid to avoid bad voodoo int startFrom = 0; hPrintf("<table id='foundTracks'>\n"); // Opening view in browser button and foundTracks count #define ENOUGH_FOUND_TRACKS 10 if (tracksFound >= ENOUGH_FOUND_TRACKS) { hPrintf("<tr><td nowrap colspan=3>\n"); hPrintf("<INPUT TYPE=SUBMIT NAME='submit' VALUE='return to browser' class='viewBtn' " "style='font-size:.8em;'>"); hPrintf(" <span class='selCbCount'></span>\n"); startFrom = cartUsualInt(cart,TRACK_SEARCH_PAGER,0); if (startFrom > 0 && startFrom < tracksFound) { int countUp = 0; for (countUp=0; countUp < startFrom;countUp++) { if (slPopHead(&tracks) == NULL) // memory waste break; } } hPrintf("</td><td align='right' valign='bottom'>\n"); findTracksPageLinks(tracksFound,startFrom,0); hPrintf("</td></tr>\n"); } // Begin foundTracks table //hPrintf("<table id='foundTracks'><tr><td colspan='2'>\n"); hPrintf("<tr><td colspan='2'>\n"); hPrintf("</td><td align='right'>\n"); hPrintf("</td></tr><tr bgcolor='#%s'><td>",HG_COL_HEADER); #define PM_BUTTON \ "<IMG height=18 width=18 " \ "id='btn_%s' src='../images/%s' title='%s all found tracks'>" hPrintf(PM_BUTTON,"plus_all", "add_sm.gif", "Select"); hPrintf(PM_BUTTON,"minus_all","remove_sm.gif","Unselect"); jsOnEventById("click", "btn_plus_all", "return findTracks.checkAllWithWait(true);"); jsOnEventById("click", "btn_minus_all", "return findTracks.checkAllWithWait(false);"); hPrintf("</td><td><b>Visibility</b></td><td colspan=2> <b>Track Name</b>\n"); // Sort options? if (tracksFound >= ENOUGH_FOUND_TRACKS) { hPrintf("<span style='float:right;'>Sort:"); cgiMakeOnEventRadioButtonWithClass(TRACK_SEARCH_SORT, "0", (sortBy == sbRelevance), NULL,"click", "findTracks.sortNow(this);"); hPrintf("by Relevance"); cgiMakeOnEventRadioButtonWithClass(TRACK_SEARCH_SORT, "1", (sortBy == sbAbc), NULL,"click", "findTracks.sortNow(this);"); hPrintf("Alphabetically"); cgiMakeOnEventRadioButtonWithClass(TRACK_SEARCH_SORT, "2", (sortBy == sbHierarchy), NULL,"click", "findTracks.sortNow(this);"); hPrintf("by Hierarchy </span>\n"); } hPrintf("</td></tr>\n"); // Set up json for js functionality struct jsonElement *jsonTdbVars = newJsonObject(newHash(8)); int trackCount=0; boolean containerTrackCount = 0; struct slRef *ptr; struct slName *connectedHubIds = hubConnectHubsInCart(cart); while((ptr = slPopHead(&tracks))) { if (++trackCount > MAX_FOUND_TRACKS) break; struct track *track = (struct track *) ptr->val; jsonTdbSettingsBuild(jsonTdbVars, track, FALSE); // FALSE: No config from track search char *hubId = NULL; if (isHubTrack(track->track)) { char *trackNameCopy = cloneString(track->track); hubId = strchr(trackNameCopy, '_'); hubId += 1; char *ptr2 = strchr(hubId, '_'); if (ptr2 == NULL) errAbort("hub track '%s' not in correct format", track->track); *ptr2 = '\0'; char *hubUrl = hashFindVal(hubIdsToUrls, hubId); if (hubUrl != NULL) { struct jsonElement *ele = jsonFindNamedField(jsonTdbVars, "", track->track); jsonObjectAdd(ele, "hubUrl", newJsonString(hubUrl)); } } if (tdbIsFolder(track->tdb)) // supertrack hPrintf("<tr class='bgLevel4' valign='top' class='found'>\n"); else if (tdbIsContainer(track->tdb)) hPrintf("<tr class='bgLevel3' valign='top' class='found'>\n"); else hPrintf("<tr class='bgLevel2' valign='top' class='found'>\n"); hPrintf("<td align='center'>\n"); // Determine visibility and checked state track->visibility = tdbVisLimitedByAncestors(cart, track->tdb, TRUE, TRUE); boolean checked = ( track->visibility != tvHide ); if (tdbIsContainerChild(track->tdb)) { // Don't need all 4 states here. Visible=checked&&enabled checked = fourStateVisible(subtrackFourStateChecked(track->tdb,cart)); // Checked is only if subtrack level vis is also set! checked = (checked && ( track->visibility != tvHide )); } // if we haven't already connected to this hub, then by default // we need to unselect every checkbox if (isHubTrack(track->track) && !slNameInList(connectedHubIds, hubId)) checked = FALSE; // Setup the check box #define CB_HIDDEN_VAR "<INPUT TYPE=HIDDEN disabled=true NAME='%s_sel' VALUE='%s'>" // subtracks and folder children get "_sel" var. ("_sel" var is temp on folder children) if (tdbIsContainerChild(track->tdb) || tdbIsFolderContent(track->tdb)) hPrintf(CB_HIDDEN_VAR,track->track,checked?"1":CART_VAR_EMPTY); #define CB_SEEN "<INPUT TYPE=CHECKBOX id='%s_sel_id' VALUE='on' class='selCb' %s>" hPrintf(CB_SEEN,track->track,(checked ? " CHECKED" : "")); safef(id, sizeof id, "%s_sel_id", track->track); // XSS Filter? jsOnEventById("click", id, "findTracks.clickedOne(this,true);"); hPrintf("</td><td>\n"); // Setup the visibility drop down #define VIS_HIDDEN_VAR "<INPUT TYPE=HIDDEN disabled=true NAME='%s' VALUE='%s'>" hPrintf(VIS_HIDDEN_VAR,track->track,CART_VAR_EMPTY); // All tracks get vis hidden var safef(id, sizeof id, "%s_id", track->track); // XSS Filter? safef(javascript, sizeof javascript, "findTracks.changeVis(this);"); struct slPair *event = slPairNew("change", cloneString(javascript)); if (tdbIsFolder(track->tdb)) { hideShowDropDownWithClassAndExtra(track->track, id, (track->visibility != tvHide), "normalText visDD", event); } else { hTvDropDownClassWithJavascript(NULL, id, track->visibility,track->canPack, "normalText seenVis",event); } // If this is a container track, allow configuring... if (tdbIsContainer(track->tdb) || tdbIsFolder(track->tdb)) { containerTrackCount++; // Using onclick ensures return to search tracks on submit hPrintf(" <IMG SRC='../images/folderWrench.png' style='cursor:pointer;' " "id='%s_confSet' title='Configure this track container...' " "> ", track->track); safef(id, sizeof id, "%s_confSet", track->track); // XSS Filter? char hubConfigUrl[4096]; safef(hubConfigUrl, sizeof(hubConfigUrl), "%s", track->track); if (isHubTrack(track->track)) { char *hubUrl = hashFindVal(hubIdsToUrls, hubId); if (hubUrl != NULL) safefcat(hubConfigUrl, sizeof(hubConfigUrl), "&hubUrl=%s", hubUrl); } jsOnEventByIdF("click", id, "findTracks.configSet(\"%s\");", hubConfigUrl); } //#define SHOW_PARENT_FOLDER #ifdef SHOW_PARENT_FOLDER else if (tdbIsContainerChild(track->tdb) || tdbIsFolderContent(track->tdb)) { struct trackDb *parentTdb = tdbIsContainerChild(track->tdb) ? tdbGetContainer(track->tdb) : tdbGetImmediateFolder(track->tdb); if (parentTdb != NULL) // Using href will not return to search tracks on submit hPrintf(" <A HREF='../cgi-bin/hgTrackUi?g=%s'><IMG SRC='../images/folderC.png'" " title='Navigate to parent container...'></A> ", parentTdb->track); } #endif///def SHOW_PARENT_FOLDER hPrintf("</td>\n"); // shortLabel has description popup and longLabel has "..." metadata char hgTrackUiUrl[4096]; safef(hgTrackUiUrl, sizeof(hgTrackUiUrl), "%s", trackUrl(track->track, NULL)); if (isHubTrack(track->track)) { char *hubUrl = hashFindVal(hubIdsToUrls, hubId); if (hubUrl != NULL) safefcat(hgTrackUiUrl, sizeof(hgTrackUiUrl), "&hubUrl=%s", hubUrl); } hPrintf("<td><a target='_top' id='%s_dispFndTrk' " "href='%s' title='Display track details'>%s</a></td>\n", track->track, hgTrackUiUrl, track->shortLabel); safef(id, sizeof id, "%s_dispFndTrk", track->track); jsOnEventByIdF("click", id, "popUp.hgTrackUi('%s',true); return false;", track->track); hPrintf("<td>%s", track->longLabel); compositeMetadataToggle(database, track->tdb, NULL, TRUE, FALSE); hPrintf("</td></tr>\n"); } //hPrintf("</table>\n"); // Closing view in browser button and foundTracks count hPrintf("<tr><td nowrap colspan=3>"); hPrintf("<INPUT TYPE=SUBMIT NAME='submit' VALUE='Return to Browser' class='viewBtn' " "style='font-size:.8em;'>"); hPrintf(" <span class='selCbCount'></span>"); if (tracksFound >= ENOUGH_FOUND_TRACKS) { hPrintf("</td><td align='right' valign='top'>\n"); findTracksPageLinks(tracksFound,startFrom,1); hPrintf("</td></tr>\n"); } hPrintf("</table>\n"); if (containerTrackCount > 0) hPrintf("<BR><IMG SRC='../images/folderWrench.png'> Tracks so marked are containers " "which group related data tracks. Containers may need additional configuration " "(by clicking on the <IMG SRC='../images/folderWrench.png'> icon) before they can " "be viewed in the browser.<BR>\n"); //hPrintf("* Tracks so marked are containers which group related data tracks. These may " // "not be visible unless further configuration is done. Click on the * to " // "configure these.<BR><BR>\n"); hPrintf("\n</form>\n"); // be done with json jsonTdbSettingsUse(jsonTdbVars); } hPrintf("</div>"); // This div allows the clear button to empty it } void doSearchTracks(struct group *groupList) { webIncludeResourceFile("ui.dropdownchecklist.css"); jsIncludeFile("ui.dropdownchecklist.js",NULL); // This line is needed to get the multi-selects initialized jsIncludeFile("ddcl.js",NULL); struct group *group; char *groups[128]; char *labels[128]; int numGroups = 1; groups[0] = ANYLABEL; labels[0] = ANYLABEL; char *nameSearch = cartOptionalString(cart, TRACK_SEARCH_ON_NAME); char *typeSearch = cartUsualString( cart, TRACK_SEARCH_ON_TYPE,ANYLABEL); char *simpleEntry = cartOptionalString(cart, TRACK_SEARCH_SIMPLE); char *descSearch = cartOptionalString(cart, TRACK_SEARCH_ON_DESCR); char *groupSearch = cartUsualString( cart, TRACK_SEARCH_ON_GROUP,ANYLABEL); boolean doSearch = sameString(cartOptionalString(cart, TRACK_SEARCH), "Search") || cartUsualInt(cart, TRACK_SEARCH_PAGER, -1) >= 0; boolean includeHubResults = cartUsualBoolean(cart, TRACK_SEARCH_ON_HUBS, FALSE); struct sqlConnection *conn = NULL; boolean metaDbExists = FALSE; if (!isHubTrack(database)) { conn = hAllocConn(database); metaDbExists = sqlTableExists(conn, "metaDb"); } int tracksFound = 0; int cols; char buf[512]; char *currentTab = cartUsualString(cart, TRACK_SEARCH_CURRENT_TAB, "simpleTab"); enum searchTab selectedTab = (sameString(currentTab, "advancedTab") ? advancedTab : simpleTab); // NOTE: could support quotes in simple tab by detecting quotes and choosing // to use doesNameMatch() || doesDescriptionMatch() if (selectedTab == simpleTab && !isEmpty(simpleEntry)) stripChar(simpleEntry, '"'); trackList = getTrackList(&groupList, -2); // global makeGlobalTrackHash(trackList); // NOTE: This is necessary when container cfg by '*' results in vis changes // This will handle composite/view override when subtrack specific vis exists, // AND superTrack reshaping. // Subtrack settings must be removed when composite/view settings are updated parentChildCartCleanup(trackList,cart,oldVars); slSort(&groupList, gCmpGroup); +struct hash *superHash = hashNew(8); for (group = groupList; group != NULL; group = group->next) { - groupTrackListAddSuper(cart, group); + groupTrackListAddSuper(cart, group, superHash); if (group->trackList != NULL) { groups[numGroups] = cloneString(group->name); labels[numGroups] = cloneString(group->label); numGroups++; if (numGroups >= ArraySize(groups)) internalErr(); } } +hashFree(&superHash); safef(buf, sizeof(buf),"Search for Tracks in the %s %s Assembly", organism, hFreezeFromDb(database)); webStartWrapperDetailedNoArgs(cart, database, "", buf, FALSE, FALSE, FALSE, FALSE); hPrintf("<div style='max-width:1080px;'>"); hPrintf("<form action='%s' name='%s' id='%s' method='get'>\n\n", hgTracksName(),TRACK_SEARCH_FORM,TRACK_SEARCH_FORM); cartSaveSession(cart); // Creates hidden var of hgsid to avoid bad voodoo safef(buf, sizeof(buf), "%lu", clock1()); cgiMakeHiddenVar("hgt_", buf); // timestamps page to avoid browser cache hPrintf("<input type='hidden' name='db' value='%s'>\n", database); hPrintf("<input type='hidden' name='%s' id='currentTab' value='%s'>\n", TRACK_SEARCH_CURRENT_TAB, currentTab); hPrintf("<input type='hidden' name='%s' value=''>\n",TRACK_SEARCH_DEL_ROW); hPrintf("<input type='hidden' name='%s' value=''>\n",TRACK_SEARCH_ADD_ROW); hPrintf("<input type='hidden' name='%s' value=''>\n",TRACK_SEARCH_PAGER); hPrintf("<div id='tabs' style='display:none; %s'>\n<ul>\n<li><a href='#simpleTab'>" "<B style='font-size:.9em;font-family: arial, Geneva, Helvetica, san-serif;'>Search</B>" "</a></li>\n<li><a href='#advancedTab'>" "<B style='font-size:.9em;font-family: arial, Geneva, Helvetica, san-serif;'>Advanced</B>" "</a></li>\n</ul>\n<div id='simpleTab' style='max-width:inherit;'>\n", cgiBrowser()==btIE?"width:1060px;":"max-width:inherit;"); hPrintf("<table id='simpleTable' style='width:100%%; font-size:.9em;'><tr><td colspan='2'>"); hPrintf("<input type='text' name='%s' id='simpleSearch' class='submitOnEnter' value='%s' " "style='max-width:1000px; width:100%%;'>\n", TRACK_SEARCH_SIMPLE,simpleEntry == NULL ? "" : simpleEntry); jsOnEventById("keyup", "simpleSearch", "findTracks.searchButtonsEnable(true);"); hPrintf("</td></tr><td style='max-height:4px;'></td></tr></table>"); //hPrintf("</td></tr></table>"); hPrintf("<input type='submit' name='%s' id='searchSubmit' value='search' " "style='font-size:.8em;'>\n", TRACK_SEARCH); hPrintf("<input type='button'id='doSTClear1' name='clear' value='clear' class='clear' " "style='font-size:.8em;'>\n"); jsOnEventById("click", "doSTClear1", "findTracks.clear();"); hPrintf("<input type='submit' name='submit' value='cancel' class='cancel' " "style='font-size:.8em;'>\n"); hPrintf("</div>\n"); // Advanced tab hPrintf("<div id='advancedTab' style='width:inherit;'>\n" "<table id='advancedTable' cellSpacing=0 style='width:inherit; font-size:.9em;'>\n"); cols = 8; // Track Name contains hPrintf("<tr><td colspan=3></td>"); hPrintf("<td nowrap><b style='max-width:100px;'>Track Name:</b></td>"); hPrintf("<td align='right'>contains</td>\n"); hPrintf("<td colspan='%d'>", cols - 4); hPrintf("<input type='text' name='%s' id='nameSearch' class='submitOnEnter' value='%s' " "style='min-width:326px; font-size:.9em;'>", TRACK_SEARCH_ON_NAME, nameSearch == NULL ? "" : nameSearch); jsOnEventById("keyup", "nameSearch", "findTracks.searchButtonsEnable(true);"); hPrintf("</td></tr>\n"); // Description contains hPrintf("<tr><td colspan=2></td><td align='right'>and </td>"); hPrintf("<td><b style='max-width:100px;'>Description:</b></td>"); hPrintf("<td align='right'>contains</td>\n"); hPrintf("<td colspan='%d'>", cols - 4); hPrintf("<input type='text' name='%s' id='descSearch' value='%s' class='submitOnEnter' " "style='max-width:536px; width:536px; font-size:.9em;'>", TRACK_SEARCH_ON_DESCR, descSearch == NULL ? "" : descSearch); jsOnEventById("keyup", "descSearch", "findTracks.searchButtonsEnable(true);"); hPrintf("</td></tr>\n"); hPrintf("<tr><td colspan=2></td><td align='right'>and </td>\n"); hPrintf("<td><b style='max-width:100px;'>Group:</b></td>"); hPrintf("<td align='right'>is</td>\n"); hPrintf("<td colspan='%d'>", cols - 4); cgiMakeDropListFullExt(TRACK_SEARCH_ON_GROUP, labels, groups, numGroups, groupSearch, NULL, NULL, "min-width:40%; font-size:.9em;", "groupSearch"); hPrintf("</td></tr>\n"); // Track Type is (drop down) hPrintf("<tr><td colspan=2></td><td align='right'>and </td>\n"); hPrintf("<td nowrap><b style='max-width:100px;'>Data Format:</b></td>"); hPrintf("<td align='right'>is</td>\n"); hPrintf("<td colspan='%d'>", cols - 4); char **formatTypes = NULL; char **formatLabels = NULL; int formatCount = getFormatTypes(&formatLabels, &formatTypes); cgiMakeDropListFullExt(TRACK_SEARCH_ON_TYPE, formatLabels, formatTypes, formatCount, typeSearch, NULL, NULL, "'min-width:40%; font-size:.9em;", "typeSearch"); hPrintf("</td></tr>\n"); // include public hubs in output: hPrintf("<tr><td colspan=2></td><td align='right'>and </td>\n"); hPrintf("<td nowrap><b style='max-width:100px;'>Include Public Hub Search Results:</b></td>"); hPrintf("<td></td>"); hPrintf("<td colspan='%d'>", cols - 4); cgiMakeCheckBox(TRACK_SEARCH_ON_HUBS, includeHubResults); hPrintf("NOTE: Including public hubs in results may be slow"); hPrintf("</td></tr>\n"); // mdb selects struct slPair *mdbSelects = NULL; if (metaDbExists) { struct slPair *mdbVars = mdbVarsSearchable(conn,TRUE,FALSE); // Tables but not file only objects mdbSelects = mdbSelectPairs(cart, mdbVars); char *output = mdbSelectsHtmlRows(conn,mdbSelects,mdbVars,cols,FALSE); // not a fileSearch if (output) { puts(output); freeMem(output); } slPairFreeList(&mdbVars); } hPrintf("</table>\n"); hPrintf("<input type='submit' name='%s' id='searchSubmit' value='search' " "style='font-size:.8em;'>\n", TRACK_SEARCH); hPrintf("<input type='button' id='doSTClear2' name='clear' value='clear' class='clear' " "style='font-size:.8em;'>\n"); jsOnEventById("click", "doSTClear2", "findTracks.clear();"); hPrintf("<input type='submit' name='submit' value='cancel' class='cancel' " "style='font-size:.8em;'>\n"); //hPrintf("<a target='_blank' href='../goldenPath/help/trackSearch.html'>help</a>\n"); hPrintf("</div>\n"); hPrintf("</div>\n"); hPrintf("</form>\n"); hPrintf("</div>"); // Restricts to max-width:1000px; cgiDown(0.8); if (measureTiming) measureTime("Rendered tabs"); if (doSearch) { // Now search long startTime = clock1000(); if (hubIdsToUrls == NULL) hubIdsToUrls = hashNew(0); if (selectedTab==simpleTab && !isEmpty(simpleEntry)) simpleSearchForTracks(simpleEntry); else if (selectedTab==advancedTab) advancedSearchForTracks(conn,groupList,nameSearch,typeSearch,descSearch, groupSearch,mdbSelects, includeHubResults); if (measureTiming) fprintf(stdout, "<span class='timing'>Searched for tracks: %lu<br></span>", clock1000()-startTime); // Sort and Print results if (selectedTab!=filesTab) { enum sortBy sortBy = cartUsualInt(cart,TRACK_SEARCH_SORT,sbRelevance); tracksFound = slCount(tracks); if (tracksFound > 1) findTracksSort(&tracks,sortBy); displayFoundTracks(cart,tracks,tracksFound,sortBy); if (measureTiming) measureTime("Displayed found tracks"); } slPairFreeList(&mdbSelects); } hFreeConn(&conn); webNewSection("About Track Search"); if (metaDbExists) hPrintf("<p>Search for terms in track names, descriptions, groups, and ENCODE " "metadata. If multiple terms are entered, only tracks with all terms " "will be part of the results."); else hPrintf("<p>Search for terms in track descriptions, groups, and names. " "If multiple terms are entered, only tracks with all terms " "will be part of the results."); hPrintf("<BR><a target='_blank' href='../goldenPath/help/trackSearch.html'>more help</a></p>\n"); webEndSectionTables(); }