2f73d7595b0e37b159e9c9355f8e99512bf37e81
max
  Wed Sep 10 08:33:03 2025 -0700
Merging Jim Robinsons code into the kent tree. Due to whitespace changes in his IDE, I'm merging this manually.
The original code is at https://github.com/igvteam/ucsc_dev/. changes up to 48816bc are included.
Also adding an API call to get the 2bit file and code to use it.

diff --git src/hg/hubApi/list.c src/hg/hubApi/list.c
index 8dbe9f60891..77bb60992b8 100644
--- src/hg/hubApi/list.c
+++ src/hg/hubApi/list.c
@@ -710,109 +710,136 @@
                 jsonWriteObjectEnd(jw);
                 }
             }
         }
     pipelineClose(&dataPipe);
     }
 errCatchEnd(errCatch);
 if (errCatch->gotError)
     {
     apiErrAbort(err400, err400Msg, "can not find genome='%s' for endpoint '/list/files'", db);
     }
 errCatchFree(&errCatch);
 return totalBytes;
 }
 
-static void filesJsonOutput(FILE *f, char *genome, boolean textOut)
+static void filesJsonOutput(FILE *f, char *genome, char *fileType, boolean textOut)
 /* for given genome, output the URLs to files available on hgdownload
  *   can be a UCSC database genome, or a GenArk hub genome name
  */
 {
 long long itemsReturned = 0;
 boolean genArkHub = FALSE;
 char genArkUrl[PATH_MAX + 1024];
 
 if ( isGenArk(genome) )
     {
     genArkHub = TRUE;
     safef(genArkUrl, sizeof(genArkUrl), "hubs/%s", genArkPath(genome));
     }
 
 /* if UCSC genome database, it has already been proven to exist */
 
 struct jsonWrite *jw = NULL;
-if (textOut)
+boolean doContext = (cgiInt(argSkipContext)==0);
+if (textOut && doContext)
     {
     char outString[1024];
     safef(outString, sizeof(outString), "# genome: %s", genome);
     textLineOut(outString);
   safef(outString, sizeof(outString), "# rsyncHost: rsync://%s", DOWNLOAD_HOST);
     textLineOut(outString);
     }
 else
     {
     jw = apiStartOutput();
+    if (doContext)
+        {
         jsonWriteString(jw, "genome", genome);
         jsonWriteString(jw, "rsyncHost", "rsync://" DOWNLOAD_HOST);
-
+        }
     jsonWriteListStart(jw, "urlList");
     }
 
 long long totalBytes = 0;
+if (fileType)
+    {
+    char outString[1024];
     if (genArkHub)
+        {
+        safef(outString, sizeof(outString), "https://%s/%s/%s/%s.2bit", DOWNLOAD_HOST, genArkUrl, genome, genome);
+        textLineOut(outString);
+        }
+    else
+        {
+        safef(outString, sizeof(outString), "https://%s/gbdb/%s/%s.2bit", DOWNLOAD_HOST, genome, genome);
+        textLineOut(outString);
+        }
+            
+    }
+else if (genArkHub)
     {
     totalBytes = rsyncList(jw, genome, genArkUrl, &itemsReturned, textOut);
     }
 else
     {
     totalBytes = rsyncList(jw, genome, "goldenPath", &itemsReturned, textOut);
     if (itemsReturned < maxItemsOutput)
        totalBytes += rsyncList(jw, genome, "gbdb", &itemsReturned, textOut);
     if (itemsReturned < maxItemsOutput)
        totalBytes += rsyncList(jw, genome, "mysql", &itemsReturned, textOut);
     }
 
 if (textOut)
     {
     char outString[1024];
+    if (doContext)
+        {
         safef(outString, sizeof(outString), "# totalBytes: %lld", totalBytes);
         textLineOut(outString);
-    if (itemsReturned > maxItemsOutput)
+        }
+    if ((itemsReturned > maxItemsOutput) && doContext)
 	{
         safef(outString, sizeof(outString), "# maxItemLimit: TRUE");
         textLineOut(outString);
         safef(outString, sizeof(outString), "# itemsReturned: %d", maxItemsOutput);
         textLineOut(outString);
 	}
     else
 	{
+        if (doContext)
+            {
        safef(outString, sizeof(outString), "# itemsReturned: %lld", itemsReturned);
             textLineOut(outString);
             }
+	}
     textFinishOutput();
     }
 else
     {
     jsonWriteListEnd(jw);
+    if (doContext)
+        {
         jsonWriteNumber(jw, "totalBytes", totalBytes);
         if (itemsReturned > maxItemsOutput)
             {
             jsonWriteBoolean(jw, "maxItemsLimit", TRUE);
             jsonWriteNumber(jw, "itemsReturned", maxItemsOutput);
             }
         else
             jsonWriteNumber(jw, "itemsReturned", itemsReturned);
+        }
     apiFinishOutput(0, NULL, jw);
     }
 }
 
 static void chromInfoJsonOutput(FILE *f, char *db)
 /* for given db, if there is a track, list the chromosomes in that track,
  * for no track, simply list the chromosomes in the sequence
  */
 {
 char *splitSqlTable = NULL;
 struct hTableInfo *tableInfo = NULL;
 char *chromName = NULL;
 char *table = cgiOptionalString("track");
 char *bigDataUrl = NULL;
 struct trackDb *thisTrack = NULL;
@@ -1235,33 +1262,34 @@
 	{
         schemaJsonOutput(stdout, db, track);
 	return;
 	}
     else
 	{
         hubSchemaJsonOutput(stdout, hubUrl, genome, track);
 	return;
 	}
     }
 else if (sameWord("files", words[1]))
     {
     boolean textOut = FALSE;
     char *extraArgs = verifyLegalArgs(argListFiles);
     if (extraArgs)
-	apiErrAbort(err400, err400Msg, "extraneous arguments found for function /list/files '%s', only 'genome' and 'format' is allowed.", extraArgs);
+	apiErrAbort(err400, err400Msg, "extraneous arguments found for function /list/files '%s', only 'genome', 'fileType', 'skipContext' and 'format' are allowed.", extraArgs);
 
     char *genome = cgiOptionalString("genome");
     char *format = cgiOptionalString("format");
+    char *fileType = cgiOptionalString("fileType");
     if (isEmpty(genome))
         apiErrAbort(err400, err400Msg, "must supply a genome name for endpoint '/list/files' (a database name or GenArk genome name, e.g.: 'hg38' or 'GCA_021951015.1'");
     if (isNotEmpty(format))
 	{
 	if (sameWord("text", format))
 	    textOut = TRUE;
         else
 	    apiErrAbort(err400, err400Msg, "only format=text allowed for endpoint '/list/files', found: format=%s", format);
 	}
-    filesJsonOutput(stdout, genome, textOut);
+    filesJsonOutput(stdout, genome, fileType, textOut);
     }
 else
     apiErrAbort(err400, err400Msg, "do not recognize endpoint function: '/%s/%s'", words[0], words[1]);
 }	/*	void apiList(char *words[MAX_PATH_INFO])        */