6e5ee11ca95cd971984038cf65bae00d9c898707
galt
  Wed Jun 4 15:40:02 2014 -0700
Since we have git, it is easy to rename errabort.c to errAbort.c without losing any history.
diff --git src/hg/lib/customTrack.c src/hg/lib/customTrack.c
index ab91741..912de8f 100644
--- src/hg/lib/customTrack.c
+++ src/hg/lib/customTrack.c
@@ -1,1060 +1,1060 @@
 /* Data structure for dealing with custom tracks in the browser.
  * See also customFactory, which is where the parsing is done. */
 
 /* Copyright (C) 2014 The Regents of the University of California 
  * See README in this or parent directory for licensing information. */
 
 #include "common.h"
 #include "hash.h"
 #include "obscure.h"
 #include "memalloc.h"
 #include "portable.h"
-#include "errabort.h"
+#include "errAbort.h"
 #include "errCatch.h"
 #include "linefile.h"
 #include "sqlList.h"
 #include "jksql.h"
 #include "customTrack.h"
 #include "ctgPos.h"
 #include "psl.h"
 #include "gff.h"
 #include "genePred.h"
 #include "net.h"
 #include "hdb.h"
 #include "hui.h"
 #include "cheapcgi.h"
 #include "wiggle.h"
 #include "hgConfig.h"
 #include "customFactory.h"
 #include "trashDir.h"
 #include "jsHelper.h"
 
 
 /* Track names begin with track and then go to variable/value pairs.  The
  * values must be quoted if they include white space. Defined variables are:
  *  name - any text up to 15 letters.
  *  description - any text up to 60 letters.
  *  url - URL.  If it contains '$$' this will be substituted with itemName.
  *  visibility - 0=hide, 1=dense, 2=full, 3=pack, 4=squish
  *  useScore - 0=use colors. 1=use grayscale based on score.
  *  color = R,G,B,  main color, should be dark.  Components from 0-255.
  *  altColor = R,G,B secondary color.
  *  priority = number.
  */
 
 struct trackDb *customTrackTdbDefault()
 /* Return default custom table: black, dense, etc. */
 {
 struct trackDb *tdb;
 
 AllocVar(tdb);
 tdb->shortLabel = cloneString(CT_DEFAULT_TRACK_NAME);
 tdb->longLabel = cloneString(CT_DEFAULT_TRACK_DESCR);
 
 tdb->table = customTrackTableFromLabel(tdb->shortLabel);
 tdb->track = cloneString(tdb->table);
 tdb->visibility = tvDense;
 tdb->grp = cloneString("user");
 tdb->type = (char *)NULL;
 tdb->canPack = 2;       /* unknown -- fill in later when type is known */
 return tdb;
 }
 
 static void createMetaInfo(struct sqlConnection *conn)
 /*	create the metaInfo table in customTrash db	*/
 {
 struct dyString *dy = newDyString(1024);
 sqlDyStringPrintf(dy, "CREATE TABLE %s (\n"
     "name varchar(255) not null,\n"
     "useCount int not null,\n"
     "lastUse datetime not null,\n"
     "PRIMARY KEY(name)\n"
     ")\n", 
     CT_META_INFO);
 sqlUpdate(conn,dy->string);
 dyStringFree(&dy);
 }
 
 void ctTouchLastUse(struct sqlConnection *conn, char *table,
 	boolean status)
 /* for status==TRUE - update metaInfo information for table
  * for status==FALSE - delete entry for table from metaInfo table
  */
 {
 static boolean exists = FALSE;
 if (!exists)
     {
     if (!sqlTableExists(conn, CT_META_INFO))
 	createMetaInfo(conn);
     exists = TRUE;
     }
 char query[1024];
 if (status)
     {
     struct sqlResult *sr = NULL;
     char **row = NULL;
     sqlSafef(query, sizeof(query), "SELECT useCount FROM %s WHERE name=\"%s\"",
 	CT_META_INFO, table);
     sr = sqlGetResult(conn,query);
     row = sqlNextRow(sr);
     if (row)
 	{
 	int useCount = sqlUnsigned(row[0]);
 	sqlFreeResult(&sr);
 	sqlSafef(query, sizeof(query), "UPDATE %s SET useCount=%d,lastUse=now() WHERE name=\"%s\"",
 	    CT_META_INFO, useCount+1, table);
 	sqlUpdate(conn,query);
 	}
     else
 	{
 	sqlFreeResult(&sr);
 	sqlSafef(query, sizeof(query), "INSERT %s VALUES(\"%s\",1,now())",
 	    CT_META_INFO, table);
 	sqlUpdate(conn,query);
 	}
     }
 else
     {
     sqlSafef(query, sizeof(query), "DELETE FROM %s WHERE name=\"%s\"",
 	CT_META_INFO, table);
     sqlUpdate(conn,query);
     }
 }
 
 boolean verifyWibExists(struct sqlConnection *conn, char *table)
 /* given a ct database wiggle table, see if the wib file is there */
 {
 char query[1024];
 sqlSafef(query, sizeof(query), "SELECT file FROM %s LIMIT 1", table);
 char **row = NULL;
 struct sqlResult *sr = NULL;
 sr = sqlGetResult(conn,query);
 row = sqlNextRow(sr);
 if (row)
     {
     if (fileExists(row[0]))
 	{
 	sqlFreeResult(&sr);
 	return TRUE;
 	}
     }
 sqlFreeResult(&sr);
 return FALSE;
 }
 
 boolean ctDbTableExists(struct sqlConnection *conn, char *table)
 /* verify if custom trash db table exists, touch access stats */
 {
 boolean status = sqlTableExists(conn, table);
 ctTouchLastUse(conn, table, status);
 return status;
 }
 
 boolean ctDbUseAll()
 /* check if hg.conf says to try DB loaders for all incoming data tracks */
 {
 static boolean checked = FALSE;
 static boolean enabled = FALSE;
 
 if (!checked)
     {
     char *val = cfgOptionDefault("customTracks.useAll", NULL);
     if (val != NULL)
         enabled = sameString(val, "yes");
     checked = TRUE;
     }
 return enabled;
 }
 
 void ctAddToSettings(struct customTrack *ct, char *name, char *val)
 /*	add a variable to tdb settings */
 {
 struct trackDb *tdb = ct->tdb;
 
 if (!tdb->settingsHash)
     trackDbHashSettings(tdb);
 
 /* add or replace if already in hash */
 hashReplace(tdb->settingsHash, name, val);
 freeMem(tdb->settings);
 /* regenerate settings string */
 tdb->settings = hashToRaString(tdb->settingsHash);
 }
 
 void ctRemoveFromSettings(struct customTrack *ct, char *name)
 /*	remove a variable from tdb settings */
 {
 struct trackDb *tdb = ct->tdb;
 
 if (!tdb->settingsHash)
     trackDbHashSettings(tdb);
 
 hashMayRemove(tdb->settingsHash, name);
 
 /* regenerate settings string */
 tdb->settings = hashToRaString(tdb->settingsHash);
 }
 
 char *customTrackTableFromLabel(char *label)
 /* Convert custom track short label to table name. */
 {
 char buf[256];
 char *tmp;
 tmp = cloneString(label);
 bits32 uniquifier = hashString(tmp);
 eraseNonAlphaNum(tmp);
 safef(buf, sizeof(buf), "%s%s_%d", CT_PREFIX, tmp, (uniquifier % 9997));
 // Name is not perfectly uniq but 4 chars of uniquifier should cut down on collisions
 freeMem(tmp);
 return cloneString(buf);
 }
 
 boolean customTrackNeedsLift(struct customTrack *trackList)
 /* Return TRUE if any track in list needs a lift. */
 {
 struct customTrack *track;
 for (track = trackList; track != NULL; track = track->next)
     if (track->needsLift)
         return TRUE;
 return FALSE;
 }
 
 void customTrackLift(struct customTrack *trackList,
                                 struct hash *ctgPosHash)
 /* Lift tracks based on hash of ctgPos. */
 {
 struct hash *chromHash = newHash(8);
 struct customTrack *track;
 for (track = trackList; track != NULL; track = track->next)
     {
     struct bed *bed;
     for (bed = track->bedList; bed != NULL; bed = bed->next)
         {
 	struct ctgPos *ctg = hashFindVal(ctgPosHash, bed->chrom);
 	if (ctg != NULL)
 	    {
 	    bed->chrom = hashStoreName(chromHash, ctg->chrom);
 	    bed->chromStart += ctg->chromStart;
 	    bed->chromEnd += ctg->chromStart;
 	    }
 	}
     track->needsLift = FALSE;
     }
 }
 
 void customTrackHandleLift(char *db, struct customTrack *ctList)
 /* lift any tracks with contig coords */
 {
 if (!customTrackNeedsLift(ctList))
     return;
 
 /* Load up hash of contigs and lift up tracks. */
 struct hash *ctgHash = newHash(0);
 struct ctgPos *ctg, *ctgList = NULL;
 struct sqlConnection *conn = hAllocConn(db);
 struct sqlResult *sr = sqlGetResult(conn, "NOSQLINJ select * from ctgPos");
 char **row;
 while ((row = sqlNextRow(sr)) != NULL)
    {
    ctg = ctgPosLoad(row);
    slAddHead(&ctgList, ctg);
    hashAdd(ctgHash, ctg->contig, ctg);
    }
 customTrackLift(ctList, ctgHash);
 ctgPosFreeList(&ctgList);
 hashFree(&ctgHash);
 sqlFreeResult(&sr);
 hFreeConn(&conn);
 }
 
 boolean bogusMacEmptyChars(char *s)
 /* Return TRUE if it looks like this is just a buggy
  * Mac browser putting in bogus chars into empty text box. */
 {
 char c = *s;
 return (c != '_') && (c != '#') && !isalnum(c);
 }
 
 static int ct_nextDefaultTrackNum = 1;
 
 static boolean isDefaultTrack(struct customTrack *ct)
 /* determine if this ia an unnamed track */
 {
 return startsWith(CT_DEFAULT_TRACK_NAME, ct->tdb->shortLabel);
 }
 
 static void nextUniqueDefaultTrack(struct customTrack *ctList)
 /* find sequence number to assure uniqueness of default track,
  * by determining highest sequence number in existing track list */
 {
 struct customTrack *ct;
 int seqNum = 0, maxFound = 0;
 for (ct = ctList; ct != NULL; ct = ct->next)
     {
     if (isDefaultTrack(ct))
         {
 	char *p = ct->tdb->shortLabel;
 	seqNum = 0;
 	while (seqNum == 0)
 	    {
 	    p = skipToNumeric(p);
 	    if (*p)
 		{
 		char *q = skipNumeric(p);
 		if (*q)
 		    p = q;
 		else
 		    seqNum = sqlSigned(p);
 		}
 	    else
 		seqNum = 1;
 	    }
         maxFound = max(seqNum, maxFound);
         }
     }
 ct_nextDefaultTrackNum = maxFound + 1;
 }
 
 static void makeUniqueDefaultTrack(struct customTrack *ct)
 /* add sequence number to track labels */
 {
 char *prev;
 char buf[256];
 
 if (ct_nextDefaultTrackNum == 1)
     /* no need for suffix for first unnamed track */
     return;
 
 prev = ct->tdb->shortLabel;
 safef(buf, sizeof(buf), "%s %d", prev, ct_nextDefaultTrackNum);
 ct->tdb->shortLabel = cloneString(buf);
 freeMem(prev);
 
 prev = ct->tdb->longLabel;
 safef(buf, sizeof(buf), "%s %d", prev, ct_nextDefaultTrackNum);
 ct->tdb->longLabel = cloneString(buf);
 freeMem(prev);
 
 freez(&ct->tdb->table);
 ct->tdb->table = customTrackTableFromLabel(ct->tdb->shortLabel);
 
 freez(&ct->tdb->track);
 ct->tdb->track = cloneString(ct->tdb->table);
 }
 
 struct customTrack *customTrackAddToList(struct customTrack *ctList,
                                          struct customTrack *addCts,
                                          struct customTrack **retReplacedCts,
                                          boolean makeDefaultUnique)
 /* add new tracks to the custom track list, removing older versions,
  * and saving the replaced tracks in a list for the caller */
 {
 struct hash *ctHash = hashNew(5);
 struct customTrack *newCtList = NULL, *oldCtList = NULL, *replacedCts = NULL;
 struct customTrack *ct = NULL, *nextCt = NULL;
 
 /* determine next sequence number for default tracks */
 nextUniqueDefaultTrack(ctList);
 
 /* process new tracks first --
  * go in reverse order and use first encountered (most recent version) */
 slReverse(&addCts);
 for (ct = addCts; ct != NULL; ct = nextCt)
     {
     nextCt = ct->next;
     if (hashLookup(ctHash, ct->tdb->track))
         freeMem(ct);
     else
         {
         if (isDefaultTrack(ct) && makeDefaultUnique)
             makeUniqueDefaultTrack(ct);
         slAddHead(&newCtList, ct);
         hashAdd(ctHash, ct->tdb->track, ct);
         }
     }
 /* add in older tracks that haven't been replaced by newer */
 for (ct = ctList; ct != NULL; ct = nextCt)
     {
     struct customTrack *newCt;
     nextCt = ct->next;
     if ((newCt = hashFindVal(ctHash, ct->tdb->track)) != NULL)
         {
         slAddHead(&replacedCts, ct);
         }
     else
         {
         slAddHead(&oldCtList, ct);
         hashAdd(ctHash, ct->tdb->track, ct);
         }
     }
 slReverse(&oldCtList);
 slReverse(&replacedCts);
 newCtList = slCat(newCtList, oldCtList);
 hashFree(&ctHash);
 if (retReplacedCts)
     *retReplacedCts = replacedCts;
 return newCtList;
 }
 
 
 
 struct customTrack *customTrackRemoveUnavailableFromList(struct customTrack *ctList)
 /* Remove from list unavailable remote resources.
  * This must be done after the custom-track bed file has been saved. */
 {
 struct customTrack *newCtList = NULL, *ct = NULL, *nextCt = NULL;
 
 /* Remove from list unavailable remote resources. */
 for (ct = ctList; ct != NULL; ct = nextCt)
     {
     nextCt = ct->next;
     if (!ct->networkErrMsg)
         {
         slAddHead(&newCtList, ct);
         }
     }
 slReverse(&newCtList);
 return newCtList;
 }
 
 char *customTrackUnavailableErrsFromList(struct customTrack *ctList)
 /* Find network errors of unavailable remote resources from list. */
 {
 struct customTrack *ct = NULL;
 struct dyString *ds = dyStringNew(0);
 char *sep = "";
 for (ct = ctList; ct != NULL; ct = ct->next)
     {
     if (ct->networkErrMsg)
 	{
 	dyStringPrintf(ds, "%s%s", sep, ct->networkErrMsg);
 	sep = "<br>\n";
 	}
     }
 char *result = dyStringCannibalize(&ds);
 if (sameOk(result,""))
     result = NULL;
 return result;
 }
 
 
 char *customTrackFileVar(char *database)
 /* return CGI var name containing custom track filename for a database */
 {
 char buf[64];
 safef(buf, sizeof buf, "%s%s", CT_FILE_VAR_PREFIX, database);
 return cloneString(buf);
 }
 
 /*	settings string is a set of lines
  *	the lines need to be output as name='value'
  *	pairs all on a single line
  */
 static void saveSettings(FILE *f, char *settings)
 {
 struct lineFile *lf;
 char *line;
 
 lf = lineFileOnString("settings", TRUE, settings);
 while (lineFileNext(lf, &line, NULL))
     {
     if (line[0] != '\0')
 	{
 	char *blank;
 	blank = strchr(line, ' ');
 	if (blank != (char *)NULL)
 	    {
 	    int nameLen = blank - line;
 	    char name[256];
 
 	    nameLen = (nameLen < 256) ? nameLen : 255;
 	    strncpy(name, line, nameLen);
 	    name[nameLen] = '\0';
 	    fprintf(f, "\t%s='%s'", name, makeEscapedString(blank+1, '\''));
 	    }
 	else
 	    fprintf(f, "\t%s", line);
 	}
     }
 lineFileClose(&lf);
 }
 
 static void saveTdbLine(FILE *f, char *fileName, struct trackDb *tdb )
 /* Write 'track' line that save trackDb info.  Only
  * write parts that aren't default, then remove from settings
  * to avoid duplication of output.
  * NOTE: that there may no longer be any need to write anything
  * out except for settings, but this is more conservative to
  * maintain functionality while custom track work continues */
 {
 struct trackDb *def = customTrackTdbDefault();
 
 if (!tdb->settingsHash)
     trackDbHashSettings(tdb);
 
 fprintf(f, "track");
 
 /* these names might be coming in from hgTables, make the names safe */
 stripChar(tdb->shortLabel,'"');	/*	no quotes please	*/
 stripChar(tdb->shortLabel,'\'');	/*	no quotes please	*/
 fprintf(f, "\t%s='%s'", "name", tdb->shortLabel);
 hashMayRemove(tdb->settingsHash, "name");
 
 stripChar(tdb->longLabel,'"');	/*	no quotes please	*/
 stripChar(tdb->longLabel,'\'');	/*	no quotes please	*/
 fprintf(f, "\t%s='%s'", "description", tdb->longLabel);
 hashMayRemove(tdb->settingsHash, "description");
 
 if (tdb->url != NULL && tdb->url[0])
     fprintf(f, "\t%s='%s'", "url", tdb->url);
 hashMayRemove(tdb->settingsHash, "url");
 if (tdb->visibility != def->visibility)
     fprintf(f, "\t%s='%d'", "visibility", tdb->visibility);
 hashMayRemove(tdb->settingsHash, "visibility");
 if (tdb->useScore != def->useScore)
     fprintf(f, "\t%s='%d'", "useScore", tdb->useScore);
 hashMayRemove(tdb->settingsHash, "useScore");
 if (tdb->priority != def->priority)
     fprintf(f, "\t%s='%.3f'", "priority", tdb->priority);
 hashMayRemove(tdb->settingsHash, "priority");
 if (tdb->colorR != def->colorR || tdb->colorG != def->colorG || tdb->colorB != def->colorB)
     fprintf(f, "\t%s='%d,%d,%d'", "color", tdb->colorR, tdb->colorG, tdb->colorB);
 hashMayRemove(tdb->settingsHash, "color");
 if (tdb->altColorR != def->altColorR || tdb->altColorG != def->altColorG
 	|| tdb->altColorB != tdb->altColorB)
     fprintf(f, "\t%s='%d,%d,%d'", "altColor", tdb->altColorR, tdb->altColorG, tdb->altColorB);
 hashMayRemove(tdb->settingsHash, "altColor");
 
 if (tdb->settings && (strlen(tdb->settings) > 0))
     saveSettings(f, hashToRaString(tdb->settingsHash));
 fputc('\n', f);
 fflush(f);
 if (ferror(f))
     errnoAbort("Write error to %s", fileName);
 trackDbFree(&def);
 }
 
 void customTracksSaveFile(char *genomeDb, struct customTrack *trackList, char *fileName)
 /* Save out custom tracks. This is just used internally
  * and by testing programs */
 {
 FILE *f = mustOpen(fileName, "w");
 
 #ifdef DEBUG
 struct dyString *ds = dyStringNew(100);
 if (!fileExists(fileName))
     {
     dyStringPrintf(ds, "chmod 666 %s", fileName);
     system(ds->string);
     }
 #endif
 
 struct customTrack *track;
 struct dyString *ds = dyStringNew(0);
 for (track = trackList; track != NULL; track = track->next)
     {
     /* may be coming in here from the table browser.  It has wiggle
      *	ascii data waiting to be encoded into .wib and .wig
      */
     if (track->wigAscii)
         {
         /* HACK ALERT - calling private method function in customFactory.c */
         track->maxChromName = hGetMinIndexLength(genomeDb); /* for the loaders */
 #ifdef PROGRESS_METER
 	track->progressFile = 0;
 #endif
         wigLoaderEncoding(track, track->wigAscii, ctDbUseAll());
         ctAddToSettings(track, "tdbType", track->tdb->type);
         ctAddToSettings(track, "wibFile", track->wibFile);
         }
 
     /* handle track description */
     if (isNotEmpty(track->tdb->html))
         {
         /* write doc file in trash and add reference to the track line*/
         if (!track->htmlFile)
             {
             static struct tempName tn;
             trashDirFile(&tn, "ct", CT_PREFIX, ".html");
             track->htmlFile = cloneString(tn.forCgi);
             }
         writeGulp(track->htmlFile, track->tdb->html, strlen(track->tdb->html));
         ctAddToSettings(track, "htmlFile", track->htmlFile);
         }
     else
         {
         track->htmlFile = NULL;
         ctRemoveFromSettings(track, "htmlFile");
         }
 
     saveTdbLine(f, fileName, track->tdb);
     if (!track->dbTrack)
         {
         struct bed *bed;
         for (bed = track->bedList; bed != NULL; bed = bed->next)
             bedOutputN(bed, track->fieldCount, f, '\t', '\n');
         }
     }
 dyStringFree(&ds);
 carefulClose(&f);
 }
 
 void customTracksSaveCart(char *genomeDb, struct cart *cart, struct customTrack *ctList)
 /* Save custom tracks to trash file for database in cart */
 {
 char *ctFileName = NULL;
 char *ctFileVar = customTrackFileVar(cartString(cart, "db"));
 if (ctList)
     {
     if (!customTracksExist(cart, &ctFileName))
         {
         /* expired custom tracks file */
         static struct tempName tn;
 	trashDirFile(&tn, "ct", CT_PREFIX, ".bed");
         ctFileName = tn.forCgi;
         cartSetString(cart, ctFileVar, ctFileName);
         }
     customTracksSaveFile(genomeDb, ctList, ctFileName);
     }
 else
     {
     /* no custom tracks remaining for this assembly */
     cartRemove(cart, ctFileVar);
     cartRemovePrefix(cart, CT_PREFIX);
     }
 }
 
 boolean customTrackIsCompressed(char *fileName)
 /* test for file suffix indicating compression */
 {
 char *fileNameDecoded = cloneString(fileName);
 cgiDecode(fileName, fileNameDecoded, strlen(fileName));
 boolean result = 
     (endsWith(fileNameDecoded,".gz") || 
      endsWith(fileNameDecoded,".Z")  ||
      endsWith(fileNameDecoded,".bz2"));
 freeMem(fileNameDecoded);
 return result;
 }
 
 static char *prepCompressedFile(struct cart *cart, char *fileName,
                                         char *binVar, char *fileVar)
 /* determine compression type and format properly for parser */
 {
     if (!customTrackIsCompressed(fileName))
     return NULL;
 char buf[256];
 char *cFBin = cartOptionalString(cart, binVar);
 if (cFBin)
     {
     safef(buf,sizeof(buf),"compressed://%s %s", fileName,  cFBin);
     /* cgi functions preserve binary data, cart vars have been
      *  cloneString-ed  which is bad for a binary stream that might
      * contain 0s  */
     }
 else
     {
     char *cF = cartOptionalString(cart, fileVar);
     safef(buf,sizeof(buf),"compressed://%s %lu %lu",
         fileName, (unsigned long) cF, (unsigned long) strlen(cF));
     }
 return cloneString(buf);
 }
 
 char* customTrackTypeFromBigFile(char *fileName)
 /* return most likely type for a big file name or NULL,
  * has to be freed */
 {
 // based on udc cache dir analysis by hiram in rm #12813
 if (endsWith(fileName, ".bb") || endsWith(fileName, ".bigBed") || endsWith(fileName, ".bigbed"))
     return cloneString("bigBed");
 if (endsWith(fileName, ".bw") || endsWith(fileName, ".bigWig") ||  
             endsWith(fileName, ".bigwig") || endsWith(fileName, ".bwig"))
     return cloneString("bigWig");
 if (endsWith(fileName, ".bam"))
     return cloneString("bam");
 if (endsWith(fileName, ".vcf.gz"))
     return cloneString("vcfTabix");
 return NULL;
 }
 
 boolean customTrackIsBigData(char *fileName)
 /* Return TRUE if fileName has a suffix that we recognize as a bigDataUrl track type. */
 {
 char *fileNameDecoded = cloneString(fileName);
 cgiDecode(fileName, fileNameDecoded, strlen(fileName));
 
 boolean result;
 char *type = customTrackTypeFromBigFile(fileNameDecoded);
 result = (type!=NULL);
 
 freeMem(type);
 freeMem(fileNameDecoded);
 return result;
 }
 
 static char *prepBigData(struct cart *cart, char *fileName, char *binVar, char *fileVar)
 /* Pass data's memory offset and size through to customFactory */
 {
 if (!customTrackIsBigData(fileName))
     return NULL;
 char buf[1024];
 char *cFBin = cartOptionalString(cart, binVar);
 if (cFBin)
     {
     // cFBin already contains memory offset and size (search for __binary in cheapcgi.c)
     safef(buf,sizeof(buf),"memory://%s %s", fileName, cFBin);
     }
 else
     {
     char *cF = cartOptionalString(cart, fileVar);
     safef(buf, sizeof(buf),"memory://%s %lu %lu",
 	  fileName, (unsigned long) cF, (unsigned long) strlen(cF));
     }
 return cloneString(buf);
 }
 
 boolean ctConfigUpdate(char *ctFile)
 /* CT update is needed if database has been enabled since
  * the custom tracks in this file were created.  The only way to check is
  * by file mod time, unless we add the enable time to
  * browser metadata somewhere */
 {
 if (!ctFile || !fileExists(ctFile))
     return FALSE;
 return cfgModTime() > fileModTime(ctFile);
 }
 
 struct customTrack *customTracksParseCartDetailed(char *genomeDb, struct cart *cart,
 					  struct slName **retBrowserLines,
 					  char **retCtFileName,
                                           struct customTrack **retReplacedCts,
                                           int *retNumAdded,
                                           char **retErr)
 /* Figure out from cart variables where to get custom track text/file.
  * Parse text/file into a custom set of tracks.  Lift if necessary.
  * If retBrowserLines is non-null then it will return a list of lines
  * starting with the word "browser".  If retCtFileName is non-null then
  * it will return the custom track filename.  If any existing custom tracks
  * are replaced with new versions, they are included in replacedCts.
  *
  * If there is a syntax error in the custom track this will report the
  * error */
 {
 #define CT_CUSTOM_FILE_BIN_VAR  CT_CUSTOM_FILE_VAR "__binary"
 #define CT_CUSTOM_DOC_FILE_BIN_VAR  CT_CUSTOM_DOC_FILE_VAR "__binary"
 int numAdded = 0;
 char *err = NULL;
 
 /* the hgt.customText and hgt.customFile variables contain new custom
  * tracks that have not yet been parsed */
 
 char *customText = cartOptionalString(cart, CT_CUSTOM_TEXT_ALT_VAR);
 /* parallel CGI variable, used by hgCustom, to allow javascript */
 if (!customText)
     customText = cartOptionalString(cart, CT_CUSTOM_TEXT_VAR);
 char *fileName = NULL;
 struct slName *browserLines = NULL;
 customText = skipLeadingSpaces(customText);
 if (customText && bogusMacEmptyChars(customText))
     customText = NULL;
 
 fileName = cartOptionalString(cart, CT_CUSTOM_FILE_NAME_VAR);
 char *fileContents = cartOptionalString(cart, CT_CUSTOM_FILE_VAR);
 if (isNotEmpty(fileName))
     {
     /* handle file input, optionally with compression */
     if (isNotEmpty(fileContents))
         customText = fileContents;
     else
         {
         /* file contents not available -- check for compressed */
         if (customTrackIsCompressed(fileName))
             {
             customText = prepCompressedFile(cart, fileName,
                                 CT_CUSTOM_FILE_BIN_VAR, CT_CUSTOM_FILE_VAR);
             }
 	else if (customTrackIsBigData(fileName))
 	    {
 	    // User is trying to directly upload a bigData file; pass data to
 	    // customFactory, which will alert the user that they need bigDataUrl etc.
 	    customText = prepBigData(cart, fileName, CT_CUSTOM_FILE_BIN_VAR, CT_CUSTOM_FILE_VAR);
 	    }
         else
             {
             /* unreadable file */
             struct dyString *ds = dyStringNew(0);
             dyStringPrintf(ds, "Unrecognized binary data format in file %s", fileName);
             err = dyStringCannibalize(&ds);
             }
 	}
     }
 customText = skipLeadingSpaces(customText);
 
 /* get track description from cart */
 char *html = NULL;
 char *docFileName = cartOptionalString(cart, CT_CUSTOM_DOC_FILE_NAME_VAR);
 char *docFileContents = cartOptionalString(cart, CT_CUSTOM_DOC_FILE_VAR);
 if (isNotEmpty(docFileContents))
     html = docFileContents;
 else if (isNotEmpty(docFileName))
     {
     if (customTrackIsCompressed(docFileName))
         html = prepCompressedFile(cart, docFileName,
                         CT_CUSTOM_DOC_FILE_BIN_VAR, CT_CUSTOM_DOC_FILE_VAR);
     else
         {
         /* unreadable file */
         struct dyString *ds = dyStringNew(0);
         dyStringPrintf(ds, "Can't read doc file: %s", docFileName);
         err = dyStringCannibalize(&ds);
         customText = NULL;
         }
     }
 else
     html = cartUsualString(cart, CT_CUSTOM_DOC_TEXT_VAR, "");
 html = cloneString(html);     /* do not let original cart var get eaten up */
 html = customDocParse(html);  /* this will chew up the input string */
 if(html != NULL)
     {
     char *tmp = html;
     html = jsStripJavascript(html);
     freeMem(tmp);
     }
 
 if ((strlen(html) > 50*1024) || startsWith("track ", html) || startsWith("browser ", html))
     {
     err = cloneString(
 	"Optional track documentation appears to be either too large (greater than 50k) or it starts with a track or browser line. "
 	"This is usually an indication that the data has been accidentally put into the documentation field. "
 	"Only html documentation is intended for this field. "
         "Please correct and re-submit.");
     html = NULL;  /* we do not want to save this bad value */
     customText = NULL;  /* trigger a return to the edit page */
     }
 
 struct customTrack *newCts = NULL, *ct = NULL;
 if (isNotEmpty(customText))
     {
     /* protect against format errors in input from user */
     struct errCatch *errCatch = errCatchNew();
     if (errCatchStart(errCatch))
         {
         newCts = customFactoryParse(genomeDb, customText, FALSE, &browserLines);
         if (html)
             {
             for (ct = newCts; ct != NULL; ct = ct->next)
                 if (!ctHtmlUrl(ct))
                     ct->tdb->html = cloneString(html);
             freez(&html);
             }
         customTrackHandleLift(genomeDb, newCts);
         }
     errCatchEnd(errCatch);
     if (errCatch->gotError)
         {
         char *msg = cloneString(errCatch->message->string);
 
         if (fileName && fileName[0])
             {
             struct dyString *ds = dyStringNew(0);
             dyStringPrintf(ds, "File '%s' - %s", fileName, msg);
             err = dyStringCannibalize(&ds);
             }
         else
             err = msg;
         }
     else
 	{
 	err = customTrackUnavailableErrsFromList(newCts);
 	if (err)
 	    newCts = NULL;  /* do not save the unhappy remote cts*/
 	}
     errCatchFree(&errCatch);
     }
 
 /* the 'ctfile_$db' variable contains a filename from the trash directory.
  * The file is created by hgCustom or hgTables after the custom track list
  * is created.  The filename may be reused.  The file contents are
  * custom tracks in "internal format" that have already been parsed */
 
 char *ctFileName = NULL;
 struct customTrack *ctList = NULL, *replacedCts = NULL;
 struct customTrack *nextCt = NULL;
 boolean removedCt = FALSE;
 
 /* load existing custom tracks from trash file */
 boolean changedCt = FALSE;
 if (customTracksExist(cart, &ctFileName))
     {
     /* protect against corrupted CT trash file or table */
     struct errCatch *errCatch = errCatchNew();
     if (errCatchStart(errCatch))
         {
         ctList =
             customFactoryParse(genomeDb, ctFileName, TRUE, retBrowserLines);
         }
     errCatchEnd(errCatch);
     if (errCatch->gotError)
         {
         remove(ctFileName);
         warn("Custom track error (%s): removing custom tracks",
                         errCatch->message->string);
         }
     errCatchFree(&errCatch);
 
     /* handle selected tracks -- update doc, remove, etc. */
     char *selectedTable = NULL;
     if (cartVarExists(cart, CT_DO_REMOVE_VAR))
         selectedTable = cartOptionalString(cart, CT_SELECTED_TABLE_VAR);
     else
         selectedTable = cartOptionalString(cart, CT_UPDATED_TABLE_VAR);
     if (selectedTable)
         {
         for (ct = ctList; ct != NULL; ct = nextCt)
             {
             nextCt = ct->next;
             if (sameString(selectedTable, ct->tdb->track))
                 {
                 if (cartVarExists(cart, CT_DO_REMOVE_VAR))
                     {
                     /* remove a track if requested, e.g. by hgTrackUi */
                     removedCt = TRUE;
                     slRemoveEl(&ctList, ct);
                     /* remove visibility variable */
                     cartRemove(cart, selectedTable);
                     /* remove configuration variables */
                     char buf[128];
                     safef(buf, sizeof buf, "%s.", selectedTable);
                     cartRemovePrefix(cart, buf);
                     /* remove control variables */
                     cartRemove(cart, CT_DO_REMOVE_VAR);
                     }
                 else
                     {
                     if (html && differentString(html, ct->tdb->html))
                         {
                         ct->tdb->html = html;
                         changedCt = TRUE;
                         }
                     }
                 break;
                 }
             }
         }
     cartRemove(cart, CT_SELECTED_TABLE_VAR);
     }
 
 /* merge new and old tracks */
 numAdded = slCount(newCts);
 ctList = customTrackAddToList(ctList, newCts, &replacedCts, FALSE);
 for (ct = ctList; ct != NULL; ct = ct->next)
     if (trackDbSetting(ct->tdb, CT_UNPARSED))
         {
         ctRemoveFromSettings(ct, CT_UNPARSED);
         changedCt = TRUE;
         }
 if (newCts || removedCt || changedCt || ctConfigUpdate(ctFileName))
     customTracksSaveCart(genomeDb, cart, ctList);
 
 if (cgiScriptName() && !endsWith(cgiScriptName(),"hgCustom"))
     {
     /* filter out cts that are unavailable remote resources */
     ctList = customTrackRemoveUnavailableFromList(ctList);
     }
 
 cartRemove(cart, CT_CUSTOM_TEXT_ALT_VAR);
 cartRemove(cart, CT_CUSTOM_TEXT_VAR);
 cartRemove(cart, CT_CUSTOM_FILE_VAR);
 cartRemove(cart, CT_CUSTOM_FILE_NAME_VAR);
 cartRemove(cart, CT_CUSTOM_FILE_BIN_VAR);
 cartRemove(cart, CT_CUSTOM_DOC_FILE_BIN_VAR);
 
 if (retCtFileName)
     *retCtFileName = ctFileName;
 if (retBrowserLines)
     *retBrowserLines = browserLines;
 if (retReplacedCts)
     *retReplacedCts = replacedCts;
 if (retNumAdded)
     *retNumAdded = numAdded;
 if (retErr)
     *retErr = err;
 return ctList;
 }
 
 struct customTrack *customTracksParseCart(char *genomeDb, struct cart *cart,
 					  struct slName **retBrowserLines,
 					  char **retCtFileName)
 /* Parse custom tracks from cart variables */
 {
 char *err = NULL;
 struct customTrack *ctList =
     customTracksParseCartDetailed(genomeDb, cart, retBrowserLines, retCtFileName,
                                         NULL, NULL, &err);
 if (err)
     warn("%s", err);
 return ctList;
 }
 
 boolean customTracksExist(struct cart *cart, char **retCtFileName)
 /* determine if there are any custom tracks.  Cleanup from expired tracks */
 {
 char *ctFileVar = customTrackFileVar(cartString(cart, "db"));
 char *ctFileName = cartOptionalString(cart, ctFileVar);
 if (ctFileName)
     {
     if (fileExists(ctFileName))
         {
         if (retCtFileName)
             *retCtFileName = ctFileName;
         return TRUE;
         }
     /* expired custom tracks file */
     cartRemove(cart, ctFileVar);
     cartRemovePrefix(cart, CT_PREFIX);
     }
 return FALSE;
 }
 
 boolean isCustomTrack(char *track)
 /* determine if track name refers to a custom track */
 {
 return (startsWith(CT_PREFIX, track));
 }
 
 void  customTrackDump(struct customTrack *track)
 /* Write out info on custom track to stdout */
 {
 if (track->tdb)
     printf("settings: %s<BR>\n", track->tdb->settings);
 printf("genome db: %s<BR>\n", track->genomeDb);
 printf("bed count: %d<BR>\n", slCount(track->bedList));
 printf("field count: %d<BR>\n", track->fieldCount);
 printf("maxChromName: %d<BR>\n", track->maxChromName);
 printf("needsLift: %d<BR>\n", track->needsLift);
 printf("fromPsl: %d<BR>\n", track->fromPsl);
 printf("wiggle: %d<BR>\n", track->wiggle);
 printf("dbTrack: %d<BR>\n", track->dbTrack);
 printf("dbDataLoad: %d<BR>\n", track->dbDataLoad);
 printf("dbTableName: %s<BR>\n", naForNull(track->dbTableName));
 printf("dbTrackType: %s<BR>\n", naForNull(track->dbTrackType));
 printf("wigFile: %s<BR>\n", naForNull(track->wigFile));
 printf("wibFile: %s<BR>\n", naForNull(track->wibFile));
 printf("wigAscii: %s<BR>\n", naForNull(track->wigAscii));
 printf("offset: %d<BR>\n", track->offset);
 printf("gffHelper: %p<BR>\n", track->gffHelper);
 printf("groupName: %s<BR>\n", naForNull(track->groupName));
 printf("tdb->type: %s<BR>\n", naForNull(track->tdb ? track->tdb->type : NULL));
 }
 
 struct customTrack *ctFind(struct customTrack *ctList,char *name)
 /* Find named custom track. */
 {
 struct customTrack *ct;
 for (ct=ctList;
      ct != NULL && differentString(ct->tdb->track,name);
      ct=ct->next) {}
 return ct;
 }