0722486013be8ff695612a47d218b52135357d1a
hiram
  Mon Feb 11 14:38:12 2019 -0800
now with dowload date stamps refs #18869

diff --git src/hg/hubApi/hubApi.c src/hg/hubApi/hubApi.c
index d9b8178..d67abcf 100644
--- src/hg/hubApi/hubApi.c
+++ src/hg/hubApi/hubApi.c
@@ -64,69 +64,57 @@
 static struct hash *trackCounter = NULL;
 static long totalTracks = 0;
 static boolean measureTiming = FALSE;	/* set by CGI parameters */
 static boolean allTrackSettings = FALSE;	/* checkbox setting */
 static char **shortLabels = NULL;	/* public hub short labels in array */
 struct hubPublic *publicHubList = NULL;
 static int publicHubCount = 0;
 static char *defaultHub = "Plants";
 static char *defaultDb = "ce11";
 static long enteredMainTime = 0;	/* will become = clock1000() on entry */
 		/* to allow calculation of when to bail out, taking too long */
 static long timeOutSeconds = 100;
 static boolean timedOut = FALSE;
 
 /* ######################################################################### */
-
-static void jsonStringPrint(FILE *f, char *value)
-/* escape string for output */
-{
-char *a = jsonStringEscape(value);
-if (isEmpty(a))
-    fprintf(f, "%s", "null");
-else
-    fprintf(f, "\"%s\"", a);
-freeMem(a);
-}
-
-static void jsonTagValue(FILE *f, char *tag, char *value)
-/* output one json string: "tag":"value" appropriately quoted and encoded */
-{
-fprintf(f,"\"%s\":",tag);
-jsonStringPrint(f, value);
-}
-
 static struct jsonWrite *jsonStartOutput()
 /* begin json output */
 {
+time_t timeNow = time(NULL);
+// struct tm tm;
+// gmtime_r(&timeNow, &tm);
 struct jsonWrite *jw = jsonWriteNew();
 jsonWriteObjectStart(jw, NULL);
 jsonWriteString(jw, "apiVersion", "0.1");
 jsonWriteString(jw, "source", "UCSantaCruz");
+jsonWriteDateFromUnix(jw, "downloadTime", (long long) timeNow);
+jsonWriteNumber(jw, "downloadTimeStamp", (long long) timeNow);
 return jw;
 }
 
 static void jsonErrAbort(char *format, ...)
 /* Issue an error message in json format. */
 {
 char errMsg[2048];
 va_list args;
 va_start(args, format);
 vsnprintf(errMsg, sizeof(errMsg), format, args);
-fputc('{',stdout);
-jsonTagValue(stdout, "error", errMsg);
-fputc('}',stdout);
+struct jsonWrite *jw = jsonStartOutput();
+jsonWriteString(jw, "error", errMsg);
+jsonWriteObjectEnd(jw);
+fputs(jw->dy->string,stdout);
+exit(0);
 }
 
 static void hubPublicJsonData(struct jsonWrite *jw, struct hubPublic *el)
 /* Print array data for one row from hubPublic table, order here
  * must be same as was stated in the columnName header element
  *  TODO: need to figure out how to use the order of the columns as
  *        they are in the 'desc' request
  */
 {
 jsonWriteListStart(jw, NULL);
 jsonWriteString(jw, NULL, el->hubUrl);
 jsonWriteString(jw, NULL, el->shortLabel);
 jsonWriteString(jw, NULL, el->longLabel);
 jsonWriteString(jw, NULL, el->registrationTime);
 jsonWriteNumber(jw, NULL, (long long)el->dbCount);
@@ -534,53 +522,55 @@
 
 static boolean tableColumns(struct jsonWrite *jw, char *table)
 /* output the column names for the given table
  * return: TRUE on error, FALSE on success
  */
 {
 jsonWriteListStart(jw, "columnNames");
 char query[1024];
 struct sqlConnection *conn = hConnectCentral();
 sqlSafef(query, sizeof(query), "desc %s", table);
 struct sqlResult *sr = sqlGetResult(conn, query);
 char **row;
 row = sqlNextRow(sr);
 if (NULL == row)
     {
-    jsonErrAbort("ERROR: can not 'desc' table '%s'\n", table);
+    jsonErrAbort("ERROR: can not 'desc' table '%s'", table);
     return TRUE;
     }
 while ((row = sqlNextRow(sr)) != NULL)
     jsonWriteString(jw, NULL, row[0]);
 sqlFreeResult(&sr);
 hDisconnectCentral(&conn);
 jsonWriteListEnd(jw);
 return FALSE;
 }
 
 static void jsonPublicHubs()
 /* output the hubPublic SQL table */
 {
 struct sqlConnection *conn = hConnectCentral();
 char *dataTime = sqlTableUpdate(conn, hubPublicTableName());
 hDisconnectCentral(&conn);
 time_t dataTimeStamp = sqlDateToUnixTime(dataTime);
+replaceChar(dataTime, ' ', 'T');
 struct hubPublic *el = publicHubList;
 struct jsonWrite *jw = jsonStartOutput();
 jsonWriteString(jw, "dataTime", dataTime);
 jsonWriteNumber(jw, "dataTimeStamp", (long long)dataTimeStamp);
 freeMem(dataTime);
+jsonWriteString(jw, "tableName", hubPublicTableName());
 tableColumns(jw, hubPublicTableName());
 jsonWriteListStart(jw, "publicHubData");
 for ( ; el != NULL; el = el->next )
     {
     hubPublicJsonData(jw, el);
     }
 jsonWriteListEnd(jw);
 jsonWriteObjectEnd(jw);
 fputs(jw->dy->string,stdout);
 }
 
 static int dbDbCmpName(const void *va, const void *vb)
 /* Compare two dbDb elements: name, ignore case. */
 {
 const struct dbDb *a = *((struct dbDb **)va);
@@ -603,63 +593,66 @@
     slAddHead(&dbList, el);
     }
 sqlFreeResult(&sr);
 hDisconnectCentral(&conn);
 slSort(&dbList, dbDbCmpName);
 return dbList;
 }
 
 static void jsonDbDb()
 /* output the dbDb SQL table */
 {
 struct sqlConnection *conn = hConnectCentral();
 char *dataTime = sqlTableUpdate(conn, "dbDb");
 hDisconnectCentral(&conn);
 time_t dataTimeStamp = sqlDateToUnixTime(dataTime);
+replaceChar(dataTime, ' ', 'T');
 struct dbDb *dbList = ucscDbDb();
 struct dbDb *el;
 struct jsonWrite *jw = jsonStartOutput();
 jsonWriteString(jw, "dataTime", dataTime);
 jsonWriteNumber(jw, "dataTimeStamp", (long long)dataTimeStamp);
 freeMem(dataTime);
+jsonWriteString(jw, "tableName", "dbDb");
 tableColumns(jw, "dbDb");
 jsonWriteListStart(jw, "ucscGenomes");
 for ( el=dbList; el != NULL; el = el->next )
     {
     dbDbJsonData(jw, el);
     }
 jsonWriteListEnd(jw);
 jsonWriteObjectEnd(jw);
 fputs(jw->dy->string,stdout);
 }
 
 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 *table = cgiOptionalString("track");
 struct sqlConnection *conn = hAllocConn(db);
 /* in trackDb language: track == table */
 if (table)
     {
     if (! sqlTableExists(conn, table))
 	jsonErrAbort("ERROR: endpoint: /list/chromosomes?db=%&table=%s ERROR table does not exist", db, table);
     if (sqlColumnExists(conn, table, "chrom"))
 	{
 	char *dataTime = sqlTableUpdate(conn, table);
 	time_t dataTimeStamp = sqlDateToUnixTime(dataTime);
+	replaceChar(dataTime, ' ', 'T');
         struct jsonWrite *jw = jsonStartOutput();
 	jsonWriteString(jw, "genome", db);
 	jsonWriteString(jw, "track", table);
 	jsonWriteString(jw, "dataTime", dataTime);
 	jsonWriteNumber(jw, "dataTimeStamp", (long long)dataTimeStamp);
 	freeMem(dataTime);
         struct slPair *list = NULL;
 	char query[2048];
         sqlSafef(query, sizeof(query), "select distinct chrom from %s", table);
 	struct sqlResult *sr = sqlGetResult(conn, query);
 	char **row;
 	while ((row = sqlNextRow(sr)) != NULL)
     	{
             int size = hChromSize(db, row[0]);
 	    slAddHead(&list, slPairNew(row[0], intToPt(size)));
@@ -673,88 +666,89 @@
         for ( ; el != NULL; el = el->next )
             jsonWriteNumber(jw, el->name, (long long)ptToInt(el->val));
 	jsonWriteObjectEnd(jw);	/* chromosomes */
         jsonWriteObjectEnd(jw);	/* top level */
         fputs(jw->dy->string,stdout);
 	}
     else
 	{
 	jsonErrAbort("ERROR: table '%s' is not a position table, no chromosomes for genome: '%s'", table, db);
 	}
     }
 else
     {
     char *dataTime = sqlTableUpdate(conn, "chromInfo");
     time_t dataTimeStamp = sqlDateToUnixTime(dataTime);
+    replaceChar(dataTime, ' ', 'T');
     struct chromInfo *ciList = createChromInfoList(NULL, db);
     struct chromInfo *el = ciList;
     struct jsonWrite *jw = jsonStartOutput();
     jsonWriteString(jw, "genome", db);
     jsonWriteString(jw, "dataTime", dataTime);
     jsonWriteNumber(jw, "dataTimeStamp", (long long)dataTimeStamp);
     freeMem(dataTime);
     jsonWriteNumber(jw, "chromCount", (long long)slCount(ciList));
     jsonWriteObjectStart(jw, "chromosomes");
     for ( ; el != NULL; el = el->next )
 	{
         jsonWriteNumber(jw, el->chrom, (long long)el->size);
 	}
     jsonWriteObjectEnd(jw);	/* chromosomes */
     jsonWriteObjectEnd(jw);	/* top level */
     fputs(jw->dy->string,stdout);
     }
 hFreeConn(&conn);
 }
 
 static void trackDbJsonOutput(char *db, FILE *f)
 /* return track list from specified UCSC database name */
 {
 struct sqlConnection *conn = hAllocConn(db);
 char *dataTime = sqlTableUpdate(conn, "trackDb");
 time_t dataTimeStamp = sqlDateToUnixTime(dataTime);
+replaceChar(dataTime, ' ', 'T');
 hFreeConn(&conn);
 struct trackDb *tdbList = hTrackDb(db);
 struct trackDb *el;
 struct jsonWrite *jw = jsonStartOutput();
 jsonWriteString(jw, "db", db);
 jsonWriteString(jw, "dataTime", dataTime);
 jsonWriteNumber(jw, "dataTimeStamp", (long long)dataTimeStamp);
 freeMem(dataTime);
 jsonWriteListStart(jw, "tracks");
 for (el = tdbList; el != NULL; el = el->next )
     jsonWriteString(jw, NULL, el->track);
 jsonWriteListEnd(jw);
 jsonWriteObjectEnd(jw);
 fputs(jw->dy->string,stdout);
 }	/*	static void trackDbJsonOutput(char *db, FILE *f)	*/
 
 #define MAX_PATH_INFO 32
 static void apiList(char *words[MAX_PATH_INFO])
 /* 'list' function words[1] is the subCommand */
 {
 if (sameWord("publicHubs", words[1]))
     jsonPublicHubs();
 else if (sameWord("ucscGenomes", words[1]))
     jsonDbDb();
 else if (sameWord("hubGenomes", words[1]))
     {
     char *hubUrl = cgiOptionalString("hubUrl");
     if (isEmpty(hubUrl))
-	jsonErrAbort("ERROR: must supply hubUrl='http:...' some URL to a hub for /list/genomes\n");
+	jsonErrAbort("ERROR: must supply hubUrl='http:...' some URL to a hub for /list/genomes");
 
     struct trackHub *hub = trackHubOpen(hubUrl, "");
-registrationTime
     if (hub->genomeList)
 	{
         struct jsonWrite *jw = jsonStartOutput();
 	jsonWriteString(jw, "hubUrl", hubUrl);
         jsonWriteListStart(jw, "genomes");
 	struct slName *theList = genomeList(hub, NULL, NULL);
 	slNameSort(&theList);
 	struct slName *el = theList;
 	for ( ; el ; el = el->next )
 	    {
 	    jsonWriteString(jw, NULL, el->name);
 	    }
 	jsonWriteListEnd(jw);
 	jsonWriteObjectEnd(jw);
         fputs(jw->dy->string,stdout);
@@ -765,31 +759,31 @@
     char *hubUrl = cgiOptionalString("hubUrl");
     char *genome = cgiOptionalString("genome");
     char *db = cgiOptionalString("db");
     if (isEmpty(hubUrl) && isEmpty(db))
       jsonErrAbort("ERROR: must supply hubUrl or db name to return track list");
     if (isEmpty(hubUrl))	// missing hubUrl implies UCSC database
 	{
         trackDbJsonOutput(db, stdout);	// only need db for this function
 	return;
 	}
     if (isEmpty(genome) || isEmpty(hubUrl))
 	{
         if (isEmpty(genome))
 	    warn("# must supply genome='someName' the name of a genome in a hub for /list/tracks\n");
 	if (isEmpty(hubUrl))
-            jsonErrAbort("ERROR: must supply hubUrl='http:...' some URL to a hub for /list/genomes\n");
+            jsonErrAbort("ERROR: must supply hubUrl='http:...' some URL to a hub for /list/genomes");
 	}
     struct trackHub *hub = trackHubOpen(hubUrl, "");
     if (hub->genomeList)
 	{
 	struct slName *dbTrackList = NULL;
 	(void) genomeList(hub, &dbTrackList, genome);
         struct jsonWrite *jw = jsonStartOutput();
 	jsonWriteString(jw, "hubUrl", hubUrl);
         jsonWriteListStart(jw, "genome");
 	slNameSort(&dbTrackList);
 	struct slName *el = dbTrackList;
 	for ( ; el != NULL; el = el->next )
 	    {
             jsonWriteString(jw, NULL, el->name);
 	    }
@@ -801,62 +795,65 @@
 else if (sameWord("chromosomes", words[1]))
     {
     char *hubUrl = cgiOptionalString("hubUrl");
 //    char *genome = cgiOptionalString("genome");
     char *db = cgiOptionalString("db");
     if (isEmpty(hubUrl) && isEmpty(db))
         jsonErrAbort("ERROR: must supply hubUrl or db name to return chromosome list");
 
     if (isEmpty(hubUrl))	// missing hubUrl implies UCSC database
 	{
         chromInfoJsonOutput(stdout, db);
 	return;
 	}
     }
 else
-    jsonErrAbort("ERROR: do not recognize endpoint '/list/%s' function\n", words[1]);
+    jsonErrAbort("do not recognize endpoint: '/list/%s' request", words[1]);
 }
 
 static struct hash *apiFunctionHash = NULL;
 
 static void setupFunctionHash()
 /* initialize the apiFunctionHash */
 {
 if (apiFunctionHash)
     return;
 
 apiFunctionHash = hashNew(0);
 hashAdd(apiFunctionHash, "list", &apiList);
 }
 
 static void apiFunctionSwitch(char *pathInfo)
 /* given a pathInfo string: /command/subCommand/etc...
  *  parse that and decide on which function to acll
  */
 {
 hPrintDisable();	/* turn off all normal HTML output, doing JSON output */
 
 /* the leading slash has been removed from the pathInfo, therefore, the
  * chop will have the first word in words[0]
  */
 char *words[MAX_PATH_INFO];/*expect no more than MAX_PATH_INFO number of words*/
 int wordCount = chopByChar(pathInfo, '/', words, ArraySize(words));
 if (wordCount < 2)
-    jsonErrAbort("ERROR: no endpoint commands found ?\n");
+    jsonErrAbort("unknown endpoint command: '/%s'", pathInfo);
 
-/* TBD: need to protect this hash find from 'not found' error */
-void (*apiFunction)(char **) = hashMustFindVal(apiFunctionHash, words[0]);
+struct hashEl *hel = hashLookup(apiFunctionHash, words[0]);
+if (hel == NULL)
+    jsonErrAbort("no such command: '%s' for endpoint '/%s'", words[0], pathInfo);
+void (*apiFunction)(char **) = hel->val;
+// void (*apiFunction)(char **) = hashMustFindVal(apiFunctionHash, words[0]);
 
 (*apiFunction)(words);
 
 }	/*	static void apiFunctionSwitch(char *pathInfo)	*/
 
 static void tracksForUcscDb(char * ucscDb)
 {
 hPrintf("<p>Tracks in UCSC genome: '%s'<br>\n", ucscDb);
 struct trackDb *tdbList = hTrackDb(ucscDb);
 struct trackDb *track;
 hPrintf("<ul>\n");
 for (track = tdbList; track != NULL; track = track->next )
     {
     hPrintf("<li>%s</li>\n", track->track);
     if (allTrackSettings)
@@ -928,37 +925,37 @@
 
 long lastTime = clock1000();
 struct trackHub *hub = trackHubOpen(urlInput, "");
 if (measureTiming)
     {
     long thisTime = clock1000();
     hPrintf("<em>hub open time: %ld millis</em><br>\n", thisTime - lastTime);
     }
 
 hPrintf("<h3>ucscDb: '%s'</h2>\n", ucscDb);
 
 struct trackHubGenome *hubGenome = hub->genomeList;
 
 hPrintf("<h2>Example URLs to return json data structures:</h2>\n");
 hPrintf("<ul>\n");
-hPrintf("<li><a href='/cgi-bin/hubApi/list/publicHubs'>list public hubs</a> <em>/cgi-bin/hubApi/list/publicHubs</em></li>\n");
-hPrintf("<li><a href='/cgi-bin/hubApi/list/ucscGenomes'>list database genomes</a> <em>/cgi-bin/hubApi/list/ucscGenomes</em></li>\n");
-hPrintf("<li><a href='/cgi-bin/hubApi/list/hubGenomes?hubUrl=%s'>list genomes from specified hub</a> <em>/cgi-bin/hubApi/list/hubGenomes?hubUrl='%s'</em></li>\n", urlInput, urlInput);
-hPrintf("<li><a href='/cgi-bin/hubApi/list/tracks?hubUrl=%s&genome=%s'>list tracks from specified hub and genome</a> <em>/cgi-bin/hubApi/list/tracks?hubUrl='%s&genome=%s'</em></li>\n", urlInput, hubGenome->name, urlInput, hubGenome->name);
-hPrintf("<li><a href='/cgi-bin/hubApi/list/tracks?db=%s'>list tracks from specified UCSC database</a> <em>/cgi-bin/hubApi/list/tracks?db='%s'</em></li>\n", ucscDb, ucscDb);
-hPrintf("<li><a href='/cgi-bin/hubApi/list/chromosomes?db=%s'>list chromosomes from specified UCSC database</a> <em>/cgi-bin/hubApi/list/chromosomes?db='%s'</em></li>\n", ucscDb, ucscDb);
-hPrintf("<li><a href='/cgi-bin/hubApi/list/chromosomes?db=%s&track=gap'>list chromosomes from specified track from UCSC databaset</a> <em>/cgi-bin/hubApi/list/chromosomes?db='%s'&track=gap</em></li>\n", ucscDb, ucscDb);
+hPrintf("<li><a href='https://jsonlint.com/?json=https://hgwdev-hiram.gi.ucsc.edu/cgi-bin/hubApi/list/publicHubs' target=_blank>list public hubs</a> <em>/cgi-bin/hubApi/list/publicHubs</em></li>\n");
+hPrintf("<li><a href='https://jsonlint.com/?json=https://hgwdev-hiram.gi.ucsc.edu/cgi-bin/hubApi/list/ucscGenomes' target=_blank>list database genomes</a> <em>/cgi-bin/hubApi/list/ucscGenomes</em></li>\n");
+hPrintf("<li><a href='/cgi-bin/hubApi/list/hubGenomes?hubUrl=%s' target=_blank>list genomes from specified hub</a> <em>/cgi-bin/hubApi/list/hubGenomes?hubUrl='%s'</em></li>\n", urlInput, urlInput);
+hPrintf("<li><a href='/cgi-bin/hubApi/list/tracks?hubUrl=%s&genome=%s' target=_blank>list tracks from specified hub and genome</a> <em>/cgi-bin/hubApi/list/tracks?hubUrl='%s&genome=%s'</em></li>\n", urlInput, hubGenome->name, urlInput, hubGenome->name);
+hPrintf("<li><a href='/cgi-bin/hubApi/list/tracks?db=%s' target=_blank>list tracks from specified UCSC database</a> <em>/cgi-bin/hubApi/list/tracks?db='%s'</em></li>\n", ucscDb, ucscDb);
+hPrintf("<li><a href='/cgi-bin/hubApi/list/chromosomes?db=%s' target=_blank>list chromosomes from specified UCSC database</a> <em>/cgi-bin/hubApi/list/chromosomes?db='%s'</em></li>\n", ucscDb, ucscDb);
+hPrintf("<li><a href='/cgi-bin/hubApi/list/chromosomes?db=%s&track=gap' target=_blank>list chromosomes from specified track from UCSC databaset</a> <em>/cgi-bin/hubApi/list/chromosomes?db='%s'&track=gap</em></li>\n", ucscDb, ucscDb);
 hPrintf("</ul>\n");
 
 hPrintf("<h4>cart dump</h4>");
 hPrintf("<pre>\n");
 cartDump(cart);
 hPrintf("</pre>\n");
 
 hPrintf("<form action='%s' name='hubApiUrl' id='hubApiUrl' method='GET'>\n\n", "../cgi-bin/hubApi");
 
 hPrintf("<b>Select public hub:&nbsp;</b>");
 #define JBUFSIZE 2048
 #define SMALLBUF 256
 char javascript[JBUFSIZE];
 struct slPair *events = NULL;
 safef(javascript, sizeof(javascript), "this.lastIndex=this.selectedIndex;");