c683ecb63d721deb02fa8ab15bf66f70f1c3a326 max Sat Jul 25 18:25:00 2026 -0700 hgBlat/hgc: single-page BLAT results view with shareable alignment links Add a modern single-page BLAT results table (hgBlat.js) and a non-frameset alignment view (showSomeAlignmentModern in hgc, gated by the blatNewPage cart var). Share/reopen a result set from a durable bigPsl custom track pinned in the cart via a saved session (htcBlatAlign / loadBlatShareSessionIfAny). Factor the shared helpers into a new blatShare module (lib/blatShare.c, inc/blatShare.h). diff --git src/hg/hgBlat/hgBlat.c src/hg/hgBlat/hgBlat.c index 8a603a00fb0..0463a8d0eda 100644 --- src/hg/hgBlat/hgBlat.c +++ src/hg/hgBlat/hgBlat.c @@ -29,30 +29,35 @@ #include "hash.h" #include "botDelay.h" #include "trashDir.h" #include "trackHub.h" #include "hgConfig.h" #include "errCatch.h" #include "portable.h" #include "portable.h" #include "dystring.h" #include "chromInfo.h" #include "net.h" #include "fuzzyFind.h" #include "chromAlias.h" #include "subText.h" #include "jsHelper.h" +#include "obscure.h" +#include "jsonWrite.h" +#include "bigBed.h" +#include "bigPsl.h" +#include "blatShare.h" struct cart *cart; /* The user's ui state. */ struct hash *oldVars = NULL; boolean orgChange = FALSE; boolean dbChange = FALSE; boolean allGenomes = FALSE; boolean allResults = FALSE; boolean autoRearr = FALSE; static long enteredMainTime = 0; boolean autoBigPsl = FALSE; // DEFAULT VALUE change to TRUE in future /* for earlyBotCheck() function at the beginning of main() */ #define delayFraction 0.5 /* standard penalty is 1.0 for most CGIs */ @@ -460,48 +465,327 @@ { safef(url, sizeof(url), "%s?position=%s:%d-%d&db=%s&ss=%s+%s&%s%s", browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database, pslName, faName, uiState, unhideTrack); htmStart(stdout, "Redirecting"); jsInlineF("location.replace('%s');\n", url); printf("\n", url); htmlEnd(); } } /* forward declaration to reduce churn */ static void getCustomName(char *database, struct cart *cart, struct psl *psl, char **pName, char **pDescription); +static void printBlatHitLinks(struct psl *psl, char *database, char *browserUrl, char *hgcUrl, + char *pslName, char *faName, char *customText, char *uiState, char *unhideTrack) +/* Print the "browser", "new tab" and "details" hyperlinks for a single BLAT hit. + * Used by the classic
 "Hyperlink" listing. */
+{
+char *browserHelp = "Open a Genome Browser showing this match";
+char *helpText = "Open a Genome Browser with the BLAT results, but in a new internet browser tab";
+// new-tab icon (Font Awesome "arrow-up-right-from-square", CC BY 4.0)
+char *icon = "";
+
+if (customText)
+    {
+    printf("browser ",
+        browserHelp, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database,
+        customText, uiState, unhideTrack);
+    printf("new tab%s ",
+        helpText, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database,
+        customText, unhideTrack, icon);
+    }
+else
+    {
+    if (autoBigPsl)
+        {
+        // skip ss variable
+        printf("browser ",
+            browserHelp, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database,
+            uiState, unhideTrack);
+        printf("new tab%s ",
+            helpText, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database,
+            unhideTrack, icon);
+        }
+    else
+        {
+        printf("browser ",
+            browserHelp, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database,
+            pslName, faName, uiState, unhideTrack);
+        printf("new tab%s ",
+            helpText, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database,
+            pslName, faName, unhideTrack, icon);
+        }
+    }
+printf("",
+    hgcUrl, psl->tStart, pslName, cgiEncode(faName), psl->qName,  psl->tName,
+    psl->tStart, psl->tEnd, database, uiState);
+printf("details ");
+}
+
+static char *chromTypeNote(char *tName)
+/* Return a short explanation for _alt/_fix/_random/chrUn sequences, or NULL for a normal chrom. */
+{
+if (endsWith(tName, "_fix"))
+    return "Assembly fix patch: corrects an error in the reference assembly.";
+if (endsWith(tName, "_alt"))
+    return "Alternate haplotype: an alternate sequence for this region.";
+if (endsWith(tName, "_random"))
+    return "Unlocalized sequence: known chromosome, position not determined.";
+if (startsWith(tName, "chrUn"))
+    return "Unplaced sequence: chromosome of origin unknown.";
+return NULL;
+}
+
+static char *blatBrowserUrl(struct psl *psl, char *database, char *browserUrl,
+    char *pslName, char *faName, char *customText, char *uiState, char *unhideTrack, boolean withUiState)
+/* Return a Genome Browser URL for one BLAT hit.  withUiState appends the hgsid; it is included on
+ * the in-tab link but omitted from the new-tab link, matching the classic hyperlink behavior. */
+{
+struct dyString *dy = dyStringNew(256);
+dyStringPrintf(dy, "%s?position=%s:%d-%d&db=%s", browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database);
+if (customText)
+    dyStringPrintf(dy, "&hgt.customText=%s", customText);
+else if (!autoBigPsl && pslName != NULL)
+    dyStringPrintf(dy, "&ss=%s+%s", pslName, faName);
+if (withUiState)
+    dyStringPrintf(dy, "&%s", uiState);
+dyStringPrintf(dy, "%s", unhideTrack);
+return dyStringCannibalize(&dy);
+}
+
+static boolean pslListMultiQuery(struct psl *pslList)
+/* Return TRUE if the list contains more than one distinct query (qName). */
+{
+struct psl *psl;
+for (psl = pslList->next; psl != NULL; psl = psl->next)
+    if (!sameString(psl->qName, pslList->qName))
+        return TRUE;
+return FALSE;
+}
+
+static struct sqlConnection *blatLocusConn(char *database, struct subText **retSubList)
+/* If the database has a "locusName" table, return a fresh connection for its range queries and
+ * build the abbreviation-expansion subList; otherwise return NULL with an empty subList. */
+{
+struct subText *subList = NULL;
+struct sqlConnection *locusConn = NULL;
+if (sqlDatabaseExists(database))
+    {
+    struct sqlConnection *conn = hAllocConn(database);
+    if (sqlTableExists(conn, "locusName"))
+        {
+        locusConn = hAllocConn(database);
+        slSafeAddHead(&subList, subTextNew("ig:", "intergenic "));
+        slSafeAddHead(&subList, subTextNew("ex:", "exon "));
+        slSafeAddHead(&subList, subTextNew("in:", "intron "));
+        slSafeAddHead(&subList, subTextNew("|", "-"));
+        }
+    hFreeConn(&conn);
+    }
+*retSubList = subList;
+return locusConn;
+}
+
+static void printBlatResultsApp(struct psl *pslList, char *database, char *organism, char *browserUrl,
+    char *hgcUrl, char *pslName, char *faName, char *customText, char *uiState, char *unhideTrack,
+    struct sqlConnection *locusConn, struct subText *subList)
+/* "Table" output mode: emit the hit data as an inline JSON object plus an empty container, and let
+ * hgBlat.js build the UI (summary strip, DataTable with identity/coverage bars, detail panel).
+ * All presentation lives in hgBlat.js; this function only assembles data.
+ * On a fresh search the per-hit "Alignment details" links go to hgc's htcUserAli (which reads the
+ * ephemeral trash .pslx/.fa); on a shared-link reopen (pslName NULL) there is no trash, so they go
+ * to htcBlatAlign instead, which rebuilds each alignment from the durable bigPsl custom track. */
+{
+struct psl *psl;
+jsIncludeDataTablesLibs();
+jsIncludeFile("hgBlat.js", NULL);
+
+struct jsonWrite *jw = jsonWriteNew();
+jsonWriteObjectStart(jw, NULL);
+
+jsonWriteObjectStart(jw, "config");
+jsonWriteString(jw, "db", database);
+jsonWriteString(jw, "organism", organism);
+jsonWriteString(jw, "queryName", pslList->qName);
+jsonWriteNumber(jw, "querySize", pslList->qSize);
+jsonWriteNumber(jw, "hitCount", slCount(pslList));
+jsonWriteBoolean(jw, "multiQuery", pslListMultiQuery(pslList));
+jsonWriteBoolean(jw, "hasLocus", locusConn != NULL);
+/* Sharing a link only makes sense when a durable bigPsl custom track was made from the results
+ * (autoBigPsl); otherwise there is nothing for the shared session to reopen from. */
+jsonWriteBoolean(jw, "canShare", autoBigPsl);
+/* The classic "Old BLAT result page" view re-reads the trash .pslx from the current search, so it
+ * is only offered on a fresh search (pslName set), not on a shared-link reopen rebuilt from the
+ * durable custom track (where the trash files may be long gone). */
+jsonWriteBoolean(jw, "canOldPage", pslName != NULL);
+jsonWriteString(jw, "hgsid", cartSessionId(cart));
+jsonWriteStringf(jw, "newSearchUrl", "hgBlat?db=%s&%s", database, uiState);
+char *posStr = cartOptionalString(cart, "position");
+if (posStr != NULL)
+    {
+    jsonWriteString(jw, "backUrl", browserUrl);
+    jsonWriteString(jw, "backPos", posStr);
+    }
+struct dyString *va = dyStringNew(128);
+dyStringPrintf(va, "%s?db=%s", browserUrl, database);
+if (customText)
+    dyStringPrintf(va, "&hgt.customText=%s", customText);
+else if (!autoBigPsl && pslName != NULL)
+    dyStringPrintf(va, "&ss=%s+%s", pslName, faName);
+dyStringPrintf(va, "&%s%s", uiState, unhideTrack);
+jsonWriteString(jw, "viewAllUrl", va->string);
+dyStringFree(&va);
+jsonWriteStringf(jw, "geneUrlBase", "%s?db=%s&%s&position=", browserUrl, database, uiState);
+jsonWriteObjectEnd(jw);   // config
+
+jsonWriteListStart(jw, "hits");
+int rank = 0;
+for (psl = pslList; psl != NULL; psl = psl->next)
+    {
+    ++rank;
+    double ident = 100.0 - pslCalcMilliBad(psl, TRUE) * 0.1;
+    char *displayChromName = chromAliasGetDisplayChrom(database, cart, psl->tName);
+    char *inTabUrl = blatBrowserUrl(psl, database, browserUrl, pslName, faName, customText,
+        uiState, unhideTrack, TRUE);
+    char *newTabUrl = blatBrowserUrl(psl, database, browserUrl, pslName, faName, customText,
+        uiState, unhideTrack, FALSE);
+
+    jsonWriteObjectStart(jw, NULL);
+    jsonWriteNumber(jw, "rank", rank);
+    jsonWriteString(jw, "qName", psl->qName);
+    jsonWriteNumber(jw, "score", pslScore(psl));
+    jsonWriteDouble(jw, "identity", ident);
+    jsonWriteString(jw, "chrom", displayChromName);
+    char *note = chromTypeNote(psl->tName);
+    if (note != NULL)
+        jsonWriteString(jw, "chromNote", note);
+    jsonWriteString(jw, "strand", psl->strand);
+    jsonWriteNumber(jw, "tStart", psl->tStart + 1);
+    jsonWriteNumber(jw, "tEnd", psl->tEnd);
+    jsonWriteNumber(jw, "span", psl->tEnd - psl->tStart);
+    jsonWriteNumber(jw, "qStart", psl->qStart + 1);
+    jsonWriteNumber(jw, "qEnd", psl->qEnd);
+    jsonWriteNumber(jw, "qSize", psl->qSize);
+    jsonWriteNumber(jw, "matches", psl->match + psl->repMatch);
+    jsonWriteNumber(jw, "misMatch", psl->misMatch);
+    jsonWriteNumber(jw, "gaps", psl->qNumInsert + psl->tNumInsert);
+    jsonWriteNumber(jw, "blocks", psl->blockCount);
+    jsonWriteString(jw, "browserUrl", inTabUrl);
+    jsonWriteString(jw, "newTabUrl", newTabUrl);
+    if (pslName != NULL)
+        jsonWriteStringf(jw, "detailsUrl", "%s?o=%d&g=htcUserAli&i=%s+%s+%s&c=%s&l=%d&r=%d&db=%s&%s",
+            hgcUrl, psl->tStart, pslName, cgiEncode(faName), psl->qName, psl->tName,
+            psl->tStart, psl->tEnd, database, uiState);
+    else
+        /* Shared-link reopen: there is no trash .pslx, but the durable bigPsl custom track (now in
+         * this cart) lets hgc's htcBlatAlign rebuild the base alignment from the stored query seq. */
+        jsonWriteStringf(jw, "detailsUrl", "%s?g=htcBlatAlign&db=%s&c=%s&o=%d&l=%d&r=%d&i=%s&%s",
+            hgcUrl, database, psl->tName, psl->tStart, psl->tStart, psl->tEnd,
+            cgiEncode(psl->qName), uiState);
+    if (locusConn)
+        {
+        struct sqlResult *sr = hRangeQuery(locusConn, "locusName", psl->tName, psl->tStart, psl->tEnd, NULL, 0);
+        char **row = sqlNextRow(sr);
+        if (row != NULL)
+            {
+            char *raw = row[4];
+            char *full = subTextString(subList, raw);
+            jsonWriteString(jw, "locusText", full);
+            freeMem(full);
+            char *type = NULL, *genes = raw;
+            if (startsWith("ig:", raw))
+                { type = "intergenic"; genes = raw + 3; }
+            else if (startsWith("ex:", raw))
+                { type = "exon"; genes = raw + 3; }
+            else if (startsWith("in:", raw))
+                { type = "intron"; genes = raw + 3; }
+            if (type != NULL)
+                {
+                jsonWriteString(jw, "locusType", type);
+                jsonWriteListStart(jw, "locusGenes");
+                char *dupe = cloneString(genes);
+                char *words[128];
+                int n = chopByChar(dupe, '|', words, ArraySize(words));
+                int i;
+                for (i = 0; i < n; ++i)
+                    jsonWriteString(jw, NULL, words[i]);
+                freeMem(dupe);
+                jsonWriteListEnd(jw);
+                }
+            }
+        sqlFreeResult(&sr);
+        }
+    jsonWriteObjectEnd(jw);
+    freeMem(inTabUrl);
+    freeMem(newTabUrl);
+    }
+jsonWriteListEnd(jw);    // hits
+jsonWriteObjectEnd(jw);  // root
+
+printf("
\n"); +jsInlineF("var hgBlatData = %s;\n", jw->dy->string); +jsonWriteFree(&jw); +} + +static void printNewDisplayBanner(char *uiState) +/* On the classic hyperlink results page, offer a one-click switch to the modern Table display. + * The link sets the blatNewPage cart variable (so the choice sticks for future searches) and + * reopens the current results (blatReopen) in the new format. + * The banner is on by default but can be turned off in hg.conf (blatNewPageBanner=off) to stop + * advertising the new page - without releasing new CGIs - while the display itself stays available + * to users who already opted in or use a direct link. */ +{ +if (!cfgOptionBooleanDefault("blatNewPageBanner", TRUE)) + return; +printf("
" + "" + "There is a new BLAT results page, with a sortable and filterable table of hits, " + "gene loci and query coverage." + "" + "Try the new page
\n", uiState); +} + void showAliPlaces(char *pslName, char *faName, char *customText, char *database, enum gfType qType, enum gfType tType, char *organism, boolean feelingLucky) /* Show all the places that align. */ { boolean useBigPsl = cfgOptionBooleanDefault("useBlatBigPsl", TRUE); struct lineFile *lf = pslFileOpen(pslName); struct psl *pslList = NULL, *psl; char *browserUrl = hgTracksName(); char *hgcUrl = hgcName(); char uiState[64]; char *vis; char unhideTrack[64]; char *sort = cartUsualString(cart, "sort", pslSortList[0]); char *output = cartUsualString(cart, "output", outputList[0]); boolean pslOut = startsWith("psl", output); boolean pslRawOut = sameWord("pslRaw", output); boolean jsonOut = sameWord(output, "json"); +/* The modern table is an opt-in replacement for the classic "hyperlink" results page, controlled by + * the blatNewPage cart variable (set by the "Try the new display" banner, cleared by the table's + * "Old BLAT result page" link). It does not apply to the raw psl/JSON download formats. */ +boolean tableOut = !pslOut && !pslRawOut && !jsonOut && cartUsualBoolean(cart, "blatNewPage", FALSE); sprintf(uiState, "%s=%s", cartSessionVarName(), cartSessionId(cart)); /* If user has hidden BLAT track, add a setting that will unhide the track if user clicks on a browser link. */ vis = cartOptionalString(cart, "hgUserPsl"); if (vis != NULL && sameString(vis, "hide")) snprintf(unhideTrack, sizeof(unhideTrack), "&hgUserPsl=dense"); else unhideTrack[0] = 0; while ((psl = pslNext(lf)) != NULL) { if (psl->match >= minMatchShown) slAddHead(&pslList, psl); @@ -550,33 +834,37 @@ pslTabOut(psl, stdout); if (pslRawOut) exit(0); printf("
");
     printf("
"); } else if (jsonOut) { webStartText(); pslWriteAllJson(pslList, stdout, database, TRUE); exit(0); } else // hyperlink { + if (!tableOut) + { + printNewDisplayBanner(uiState); printf("

BLAT Search Results

"); + } char* posStr = cartOptionalString(cart, "position"); - if (posStr != NULL) + if (posStr != NULL && !tableOut) printf("

Go back to %s on the Genome Browser.

\n", browserUrl, posStr); if (autoBigPsl) { char *trackName = NULL; char *trackDescription = NULL; getCustomName(database, cart, pslList, &trackName, &trackDescription); psl = pslList; char item[1024]; safef(item, sizeof item, "%s %s %s", pslName,faName,psl->qName); struct dyString *url = dyStringNew(256); dyStringPrintf(url, "http%s://%s", sameOk(getenv("HTTPS"), "on") ? "s" : "", getenv("HTTP_HOST")); dyStringPrintf(url, "%s", hgcUrl+2); @@ -763,47 +1051,39 @@ printf(" Custom track description: "); cgiMakeTextVar( "trackDescription", trackDescription,50); printf(""); printf("\n"); printInfoIcon("The BLAT results below are temporary and will be replaced by your next BLAT search. " "However, when saved as a custom track with the button on the left, BLAT results are stored on our " "servers and can be saved as stable session (View > My Sessions) links that can be shared via email or in manuscripts. " "\n

We have never cleaned up the data under stable session links so far. " "To reduce track clutter in your own sessions, you can delete BLAT custom tracks from the main Genome Browser " "view using the little trash icon next to each custom track.

"); puts(""); printf(""); } - boolean hasDb = sqlDatabaseExists(database); struct sqlConnection *locusConn = NULL; struct subText *subList = NULL; - if (hasDb) - { - struct sqlConnection *conn = hAllocConn(database); - if (cfgOptionBooleanDefault("blatShowLocus", FALSE) && sqlTableExists(conn, "locusName") ) - { - locusConn = hAllocConn(database); - slSafeAddHead(&subList, subTextNew("ig:", "intergenic ")); - slSafeAddHead(&subList, subTextNew("ex:", "exon ")); - slSafeAddHead(&subList, subTextNew("in:", "intron ")); - slSafeAddHead(&subList, subTextNew("|", "-")); - } - hFreeConn(&conn); - } + if (tableOut || cfgOptionBooleanDefault("blatShowLocus", FALSE)) + locusConn = blatLocusConn(database, &subList); + if (tableOut) + printBlatResultsApp(pslList, database, organism, browserUrl, hgcUrl, pslName, faName, customText, uiState, unhideTrack, locusConn, subList); + else + { printf("
");
 
     // find maximum query name size for padding calculations and
     // find maximum target chrom name size for padding calculations
     int maxQChromNameSize = 0;
     int maxTChromNameSize = 0;
     for (psl = pslList; psl != NULL; psl = psl->next)
 	{
 	int qLen = strlen(psl->qName);
 	maxQChromNameSize = max(maxQChromNameSize,qLen);
 	int tLen = strlen(psl->tName);
 	maxTChromNameSize = max(maxTChromNameSize,tLen);
 	}
     maxQChromNameSize = max(maxQChromNameSize,5);
     maxTChromNameSize = max(maxTChromNameSize,5);
@@ -820,72 +1100,31 @@
     printf("SCORE START   END QSIZE IDENTITY  CHROM ");
     spaceOut(stdout, maxTChromNameSize - 5);
 
     printf(" STRAND  START       END   SPAN\n");
 
     printf("----------------------------------------------------------------------------------------------------------");
     if (locusConn)
         repeatCharOut(stdout, '-', 25);
     repeatCharOut(stdout, '-', maxQChromNameSize - 5);
     repeatCharOut(stdout, '-', maxTChromNameSize - 5);
 
     printf("\n");
 
     for (psl = pslList; psl != NULL; psl = psl->next)
 	{
-        char *browserHelp = "Open a Genome Browser showing this match";
-        char *helpText = "Open a Genome Browser with the BLAT results, but in a new internet browser tab";
-        // XX putting SVG into C code like this is ugly. define somewhere? maybe have globals for these?
-        char *icon = "";
-
-
-	if (customText)
-	    {
-	    printf("browser ",
-		browserHelp, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database, 
-		customText, uiState, unhideTrack);
-	    printf("new tab%s ",
-		helpText, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database, 
-		customText, unhideTrack, icon);
-	    } 
-	else 
-	    {
-	    if (autoBigPsl)
-		{
-		// skip ss variable
-		printf("browser ",
-		    browserHelp, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database, 
-		    uiState, unhideTrack);
-		printf("new tab%s ",
-		    helpText, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database, 
-		    unhideTrack, icon);
-		}
-	    else 
-		{
-		printf("browser ",
-		    browserHelp, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database, 
-		    pslName, faName, uiState, unhideTrack);
-		printf("new tab%s ",
-		    helpText, browserUrl, psl->tName, psl->tStart + 1, psl->tEnd, database, 
-		    pslName, faName, unhideTrack, icon);
-		}
-	    }
-	printf("", 
-	    hgcUrl, psl->tStart, pslName, cgiEncode(faName), psl->qName,  psl->tName,
-	    psl->tStart, psl->tEnd, database, uiState);
-	printf("details ");
+	printBlatHitLinks(psl, database, browserUrl, hgcUrl, pslName, faName, customText, uiState, unhideTrack);
 
         // print name of this locus
         if (locusConn)
             {
             struct sqlResult *sr = hRangeQuery(locusConn, "locusName", psl->tName, psl->tStart, psl->tEnd, NULL, 0);
             char **row;
             row = sqlNextRow(sr);
             if (row != NULL)
                 {
                 char *desc = row[4];
                 char *descLong = subTextString(subList, desc);
                 printf("%-25s", descLong);
                 freeMem(descLong);
                 }
             sqlFreeResult(&sr);
@@ -909,30 +1148,31 @@
             printf("   What is chrom_fix?");
         else if (endsWith(seq, "_alt"))
             printf("   What is chrom_alt?");
         else if (endsWith(seq, "_random"))
             printf("   What is chrom_random?");
         else if (startsWith(seq, "chrUn"))
             printf("   What is a chrUn sequence?");
         printf("\n");
 	}
     printf("
\n"); webNewSection("Help"); puts("

Missing a match?
"); puts("What is chr_alt & chr_fix?

\n"); puts("
\n"); } + } pslFreeList(&pslList); } void trimUniq(bioSeq *seqList) /* Check that all seq's in list have a unique name. Try and * abbreviate longer sequence names. */ { struct hash *hash = newHash(0); bioSeq *seq; for (seq = seqList; seq != NULL; seq = seq->next) { char *saferString = needMem(strlen(seq->name)+1); char *c, *s; @@ -1969,30 +2209,35 @@ if (allGenomes) queryServer(serve->host, serve->port, db, seq, "query", xType, FALSE, FALSE, TRUE, seqNumber, serve->genomeDataDir); else { gfAlignStrand(conn, serve->nibDir, seq, TRUE, minMatchShown, tFileCache, gvo); } } gfOutputQuery(gvo, f); ++seqNumber; } carefulClose(&f); if (!allGenomes) { + /* Remember the trash result files so the Table view's "Old BLAT result page" link can + * re-render the classic hyperlink view from them within this session without re-running BLAT + * (see doOldPageReopen). These are just short paths; the query sequence is not stored. */ + cartSetString(cart, "blatPslFile", pslTn.forCgi); + cartSetString(cart, "blatFaFile", faTn.forCgi); showAliPlaces(pslTn.forCgi, faTn.forCgi, NULL, serve->db, qType, tType, organism, feelingLucky); } if ((!feelingLucky && !allGenomes) || (autoBigPsl && feelingLucky)) cartWebEnd(); gfFileCacheFree(&tFileCache); } void askForSeq(char *organism, char *db) /* Put up a little form that asks for sequence. * Call self.... */ { /* ignore struct serverTable* return, but can error out if not found */ @@ -2338,47 +2583,145 @@ if (!gH->isProt) { printf("%d\t", gfR->qFrame); } } printf("\n"); } } printf("\n"); } printf("
\n"); } +static void doShareReopen(char *database, char *organism) +/* Rebuild the Table view for a shared link (?u=&s=) from the durable bigPsl custom track that was + * saved with the session, without re-running BLAT and without any stored query sequence. The + * custom track (and its bigBed file) is kept alive by refreshNamedSessionCustomTracks for as long + * as the shared session exists, so this is durable. */ +{ +cartWebStart(cart, database, "%s (%s) BLAT Results", + trackHubSkipHubName(organism), trackHubSkipHubName(database)); +char *bbFile = blatFindPinnedBigPsl(cart); +if (bbFile == NULL || !fileExists(bbFile)) + { + printf("

These shared BLAT results are no longer available. The custom track that " + "stored them has expired or been removed. Please run a new " + "BLAT search.

\n"); + cartWebEnd(); + return; + } +struct psl *pslList = pslListFromBigPslFile(bbFile); +if (pslList == NULL) + { + printf("

These shared BLAT results contained no alignments.

\n"); + cartWebEnd(); + return; + } +pslSortListByVar(&pslList, cartUsualString(cart, "sort", pslSortList[0])); + +struct subText *subList = NULL; +struct sqlConnection *locusConn = blatLocusConn(database, &subList); + +char uiState[64]; +safef(uiState, sizeof uiState, "%s=%s", cartSessionVarName(), cartSessionId(cart)); +printBlatResultsApp(pslList, database, organism, hgTracksName(), hgcName(), + NULL, NULL, NULL, uiState, "", locusConn, subList); +cartWebEnd(); +} + +static void doReopenResults(char *database, char *organism) +/* Re-render the last search's results for the current session from the trash result files saved + * with it (see blatPslFile/blatFaFile), without re-running BLAT. showAliPlaces picks the classic + * or new-table format from the blatNewPage cart variable, so this backs both the classic page's + * "Try the new display" banner and the table's "Old BLAT result page" link. Those trash files are + * only guaranteed for the current session, so if they have been cleaned up, say so rather than + * showing a broken page. */ +{ +char *pslFile = cartOptionalString(cart, "blatPslFile"); +char *faFile = cartOptionalString(cart, "blatFaFile"); +cartWebStart(cart, database, "%s (%s) BLAT Results", + trackHubSkipHubName(organism), trackHubSkipHubName(database)); +if (pslFile == NULL || faFile == NULL || !fileExists(pslFile)) + printf("

These BLAT results are no longer available. Please run a new " + "BLAT search.

\n"); +else + showAliPlaces(pslFile, faFile, NULL, database, gftDna, gftDna, organism, FALSE); +cartWebEnd(); +} + void doMiddle(struct cart *theCart) /* Write header and body of html page. */ { char *userSeq; char *db, *organism; boolean clearUserSeq = cgiBoolean("Clear"); allGenomes = cgiVarExists("allGenomes"); cart = theCart; dnaUtilOpen(); +/* The former "table" value of the output dropdown is now the blatNewPage toggle; migrate any stale + * cart value so the dropdown always shows a valid option. */ +if (sameOk(cartOptionalString(cart, "output"), "table")) + cartSetString(cart, "output", "hyperlink"); + +/* Short "Share a link" params: u= s= load an anonymous saved session (see the + * Table-mode share button), restoring its cart (db, custom tracks, blatLastBigBed) into this one; + * doShareReopen below rebuilds the Table view from the durable bigPsl custom track. */ +if (cgiOptionalString("s") != NULL) + { + /* Viewing a shared results link shouldn't silently flip the viewer's own new-vs-classic page + * preference, so preserve blatNewPage across the (whole-cart) session load. clone first: the + * load frees the cart's current storage. */ + char *myNewPage = cloneString(cartOptionalString(cart, "blatNewPage")); + struct sqlConnection *sConn = hConnectCentral(); + cartLoadUserSession(sConn, cgiUsualString("u", "l"), cgiString("s"), cart, oldVars, NULL); + hDisconnectCentral(&sConn); + if (myNewPage != NULL) + cartSetString(cart, "blatNewPage", myNewPage); + else + cartRemove(cart, "blatNewPage"); + freeMem(myNewPage); + } + orgChange = sameOk(cgiOptionalString("changeInfo"),"orgChange"); if (orgChange) cgiVarSet("db", hDefaultDbForGenome(cgiOptionalString("org"))); getDbAndGenome(cart, &db, &organism, oldVars); chromAliasSetup(db); + +/* A shared "?u=&s=" link rebuilds the Table view from the session's durable custom track; it never + * re-runs BLAT, so short-circuit the normal query-driven flow here (before findClosestServer, which + * is only needed for an actual search). */ +if (cgiVarExists("s")) + { + doShareReopen(db, organism); + return; + } +/* The classic page's "Try the new display" banner and the Table view's "Old BLAT result page" link + * both flip the blatNewPage preference and re-render this session's saved results in the other + * format, without re-running BLAT. */ +if (cgiVarExists("blatReopen")) + { + doReopenResults(db, organism); + return; + } + char *oldDb = cloneString(db); // n.b. this changes to default db if db doesn't have BLAT findClosestServer(&db, &organism); allResults = cartUsualBoolean(cart, "allResults", allResults); autoRearr = cartUsualBoolean(cart, "autoRearr", autoRearr); /* Get sequence - from userSeq variable, or if * that is empty from a file. */ if (clearUserSeq) { cartSetString(cart, "userSeq", ""); cartSetString(cart, "seqFile", ""); } @@ -2601,31 +2944,31 @@ } else { printf("No input sequences provided.

\n"); } cartWebEnd(); } else blatSeq(skipLeadingSpaces(userSeq), organism, db, 0); } } /* Null terminated list of CGI Variables we don't want to save * permanently. */ -char *excludeVars[] = {"Submit", "submit", "Clear", "Lucky", "type", "userSeq", "seqFile", "showPage", "changeInfo", NULL}; +char *excludeVars[] = {"Submit", "submit", "Clear", "Lucky", "type", "userSeq", "seqFile", "showPage", "changeInfo", "blatReopen", "u", "s", NULL}; int main(int argc, char *argv[]) /* Process command line. */ { cfgInitCgi(); enteredMainTime = clock1000(); /* 0, 0, == use default 10 second for warning, 20 second for immediate exit */ issueBotWarning = earlyBotCheck(enteredMainTime, "hgBlat", delayFraction, 0, 0, "html"); oldVars = hashNew(10); cgiSpoof(&argc, argv); autoBigPsl = cfgOptionBooleanDefault("autoBlatBigPsl", autoBigPsl); /* org has precedence over db when changeInfo='orgChange' */