65490776b922ad5a6ba63214bc015c3f61c5fad2
jcasper
  Wed Jun 12 14:56:14 2019 -0700
Adding custom track support for hic plus a couple cosmetic changes, refs #18842

diff --git src/hg/lib/customFactory.c src/hg/lib/customFactory.c
index 6ca66a9..cec2e36 100644
--- src/hg/lib/customFactory.c
+++ src/hg/lib/customFactory.c
@@ -35,30 +35,31 @@
 #include "bigWig.h"
 #include "bigBed.h"
 #include "hgBam.h"
 #include "vcf.h"
 #include "makeItemsItem.h"
 #include "bedDetail.h"
 #include "pgSnp.h"
 #include "regexHelper.h"
 #include "chromInfo.h"
 #include "trackHub.h"
 #include "bedTabix.h"
 #include "barChartBed.h"
 #include "barChartUi.h"
 #include "interact.h"
 #include "interactUi.h"
+#include "hic.h"
 #include "cgiApoptosis.h"
 
 // placeholder when custom track uploaded file name is not known
 #define CT_NO_FILE_NAME         "custom track"
 
 static boolean doExtraChecking = FALSE;
 
 /*** Utility routines used by many factories. ***/
 
 char *customFactoryNextTilTrack(struct customPp *cpp)
 /* Return next line.  Return NULL at end of input or at line starting with
  * "track." */
 {
 char *line = customPpNext(cpp);
 if (line != NULL && startsWithWord("track", line))
@@ -132,30 +133,107 @@
 {
 if (a == NULL && b == NULL)
     errAbort("sameType should not be called when both inputs are NULL.");
 else if (a == NULL || b == NULL)
     return FALSE;
 char *aCopy = cloneString(a);
 char *bCopy = cloneString(b);
 char *aWord = firstWordInLine(aCopy);
 char *bWord = firstWordInLine(bCopy);
 boolean same = sameString(aWord, bWord);
 freeMem(aCopy);
 freeMem(bCopy);
 return same;
 }
 
+boolean isValidBigDataUrl(char *url, boolean doAbort)
+/* return True if the URL is a valid bigDataUrl.
+ * It can be a local filename if this is allowed by udc.localDir
+ */
+{
+if ((startsWith("http://", url)
+   || startsWith("https://", url)
+   || startsWith("ftp://", url)))
+return TRUE;
+
+// we allow bigDataUrl's to point to trash (or sessionDataDir, if configured)
+char *sessionDataDir = cfgOption("sessionDataDir");
+char *sessionDataDirOld = cfgOption("sessionDataDirOld");
+if (startsWith(trashDir(), url) ||
+    (isNotEmpty(sessionDataDir) && startsWith(sessionDataDir, url)) ||
+    (isNotEmpty(sessionDataDirOld) && startsWith(sessionDataDirOld, url)))
+    return TRUE;
+
+char *prefix = cfgOption("udc.localDir");
+if (prefix == NULL)
+    {
+    if (doAbort)
+        errAbort("Only network protocols http, https, or ftp allowed in bigDataUrl: '%s'", url);
+    return FALSE;
+    }
+
+if (!startsWith(prefix, url))
+    {
+    if (doAbort)
+        errAbort("bigDataUrl '%s' on local file system has to start with '%s' (see udc.localDir directive in cgi-bin/hg.conf)", url, prefix);
+    return FALSE;
+    }
+
+return TRUE;
+}
+
+static void checkAllowedBigDataUrlProtocols(char *url)
+/* Abort if url is not using one of the allowed bigDataUrl network protocols.
+ * In particular, do not allow a local file reference, unless explicitely
+ * allowed by hg.conf's udc.localDir directive. */
+{
+isValidBigDataUrl(url, TRUE);
+}
+
+static char *bigDataDocPath(char *type)
+/* If type is a bigData type, provide a relative path to its custom track/format doc page. */
+{
+char *docUrl = NULL;
+if (sameString(type, "bigWig"))
+    docUrl = "../goldenPath/help/bigWig.html";
+else if (sameString(type, "bigBed"))
+    docUrl = "../goldenPath/help/bigBed.html";
+else if (sameString(type, "bam"))
+    docUrl = "../goldenPath/help/bam.html";
+else if (sameString(type, "vcfTabix"))
+    docUrl = "../goldenPath/help/vcf.html";
+return docUrl;
+}
+
+static void requireBigDataUrl(char *bigDataUrl, char *type, char *trackName)
+/* If bigDataUrl is empty, errAbort with helpful message about bigDataUrl requirement */
+{
+if (isEmpty(bigDataUrl))
+    {
+    struct dyString *doc = dyStringNew(0);
+    char *docUrl = bigDataDocPath(type);
+    if (docUrl != NULL)
+	dyStringPrintf(doc, "  For more information about the bigDataUrl setting, see "
+		       "<A HREF=\"%s\" TARGET=_BLANK>%s custom track documentation</A>.",
+		       docUrl, type);
+    errAbort("Missing bigDataUrl setting from track of type=%s (%s).  "
+	     "Please check for case and spelling and that there is no new-line "
+	     "between the 'track' and the 'bigDataUrl' if the bigDataUrl appears to be there."
+	     "%s",
+	     type, trackName, doc->string);
+    }
+}
 /*** BED Factory ***/
 
 static boolean rowIsBed(char **row, int wordCount, char *db, struct dyString *reason)
 /* Return TRUE if row is consistent with BED format.  If it's not BED and reason is not NULL,
  * append reason for failure. */
 {
 if (wordCount < 3)
     {
     if (reason)
         dyStringAppend(reason, "Too few fields (need at least 3)");
     return FALSE;
     }
 if (wordCount > bedKnownFields)
     {
     if (reason)
@@ -1551,30 +1629,74 @@
     slAddHead(&itemList, item);
     }
 slReverse(&itemList);
 return interactFinish(track, itemList);
 }
 
 struct customFactory interactFactory =
 /* Factory for interact tracks */
 {
     NULL,
     "interact",
     interactRecognizer,
     interactLoader,
     };
 
+
+/*********************/
+/**** hic Factory ****/
+
+static boolean hicRecognizer(struct customFactory *fac,
+	struct customPp *cpp, char *type,
+    	struct customTrack *track)
+/* Return TRUE if looks like we're handling a hic track */
+{
+return (sameType(type, "hic"));
+}
+
+static struct customTrack *hicLoader(struct customFactory *fac,
+	struct hash *chromHash,
+    	struct customPp *cpp, struct customTrack *track, boolean dbRequested)
+/* Load up hic data until get next track line. */
+{
+struct hash *settings = track->tdb->settingsHash;
+char *bigDataUrl = hashFindVal(settings, "bigDataUrl");
+requireBigDataUrl(bigDataUrl, fac->name, track->tdb->shortLabel);
+checkAllowedBigDataUrlProtocols(bigDataUrl);
+
+if (doExtraChecking)
+    {
+    struct hicMeta *meta;
+    char *hicErrMsg = hicLoadHeader(bigDataUrl, &meta);
+    if (hicErrMsg != NULL)
+        {
+        track->networkErrMsg = cloneString(hicErrMsg);
+        }
+    }
+return track;
+}
+
+struct customFactory hicFactory =
+/* Factory for Hi-C tracks */
+{
+    NULL,
+    "hic",
+    hicRecognizer,
+    hicLoader,
+    };
+
+
 /*** GFF/GTF Factory - converts to BED ***/
 
 static boolean rowIsGff(char *db, char **row, int wordCount, char *type, struct lineFile *lf)
 /* Return TRUE if format of this row is consistent with being a .gff */
 {
 boolean isGff = FALSE;
 if (wordCount >= 8 && wordCount <= 9)
     {
     /* Check that strand is + - or . */
     char *strand = row[6];
     char c = strand[0];
     if ((c == '.' || c == '+' || c == '-') && strand[1] == 0)
         {
         // check chrom name
         char *officialChrom = hgOfficialChromName(db, row[0]);
@@ -2548,108 +2670,30 @@
 struct hash *settings = track->tdb->settingsHash;
 if (hashLookup(settings, "viewLimits") == NULL)
     {
     struct bbiSummaryElement sum = bbiTotalSummary(track->bbiFile);
     if (sum.minVal == sum.maxVal)
 	{
 	sum.minVal += 1;
 	sum.maxVal -= 1;
 	}
     char text[1024];
     safef(text, sizeof(text), "%f:%f", sum.minVal, sum.maxVal);
     hashAdd(settings, "viewLimits", cloneString(text));
     }
 }
 
-boolean isValidBigDataUrl(char *url, boolean doAbort)
-/* return True if the URL is a valid bigDataUrl.
- * It can be a local filename if this is allowed by udc.localDir
- */
-{
-if ((startsWith("http://", url)
-   || startsWith("https://", url)
-   || startsWith("ftp://", url)))
-return TRUE;
-
-// we allow bigDataUrl's to point to trash (or sessionDataDir, if configured)
-char *sessionDataDir = cfgOption("sessionDataDir");
-char *sessionDataDirOld = cfgOption("sessionDataDirOld");
-if (startsWith(trashDir(), url) ||
-    (isNotEmpty(sessionDataDir) && startsWith(sessionDataDir, url)) ||
-    (isNotEmpty(sessionDataDirOld) && startsWith(sessionDataDirOld, url)))
-    return TRUE;
-
-char *prefix = cfgOption("udc.localDir");
-if (prefix == NULL)
-    {
-    if (doAbort)
-        errAbort("Only network protocols http, https, or ftp allowed in bigDataUrl: '%s'", url);
-    return FALSE;
-    }
-
-if (!startsWith(prefix, url))
-    {
-    if (doAbort)
-        errAbort("bigDataUrl '%s' on local file system has to start with '%s' (see udc.localDir directive in cgi-bin/hg.conf)", url, prefix);
-    return FALSE;
-    }
-
-return TRUE;
-}
-
-static void checkAllowedBigDataUrlProtocols(char *url)
-/* Abort if url is not using one of the allowed bigDataUrl network protocols.
- * In particular, do not allow a local file reference, unless explicitely
- * allowed by hg.conf's udc.localDir directive. */
-{
-isValidBigDataUrl(url, TRUE);
-}
-
-static char *bigDataDocPath(char *type)
-/* If type is a bigData type, provide a relative path to its custom track/format doc page. */
-{
-char *docUrl = NULL;
-if (sameString(type, "bigWig"))
-    docUrl = "../goldenPath/help/bigWig.html";
-else if (sameString(type, "bigBed"))
-    docUrl = "../goldenPath/help/bigBed.html";
-else if (sameString(type, "bam"))
-    docUrl = "../goldenPath/help/bam.html";
-else if (sameString(type, "vcfTabix"))
-    docUrl = "../goldenPath/help/vcf.html";
-return docUrl;
-}
-
-static void requireBigDataUrl(char *bigDataUrl, char *type, char *trackName)
-/* If bigDataUrl is empty, errAbort with helpful message about bigDataUrl requirement */
-{
-if (isEmpty(bigDataUrl))
-    {
-    struct dyString *doc = dyStringNew(0);
-    char *docUrl = bigDataDocPath(type);
-    if (docUrl != NULL)
-	dyStringPrintf(doc, "  For more information about the bigDataUrl setting, see "
-		       "<A HREF=\"%s\" TARGET=_BLANK>%s custom track documentation</A>.",
-		       docUrl, type);
-    errAbort("Missing bigDataUrl setting from track of type=%s (%s).  "
-	     "Please check for case and spelling and that there is no new-line "
-	     "between the 'track' and the 'bigDataUrl' if the bigDataUrl appears to be there."
-	     "%s",
-	     type, trackName, doc->string);
-    }
-}
-
 static struct customTrack *bigWigLoader(struct customFactory *fac,
 	struct hash *chromHash,
     	struct customPp *cpp, struct customTrack *track, boolean dbRequested)
 /* Load up wiggle data until get next track line. */
 {
 /* Not much to this.  A bigWig has nothing here but a track line. */
 struct hash *settings = track->tdb->settingsHash;
 char *bigDataUrl = hashFindVal(settings, "bigDataUrl");
 requireBigDataUrl(bigDataUrl, fac->name, track->tdb->shortLabel);
 checkAllowedBigDataUrlProtocols(bigDataUrl);
 
 /* protect against temporary network error */
 struct errCatch *errCatch = errCatchNew();
 if (errCatchStart(errCatch))
     {
@@ -3431,30 +3475,31 @@
     slAddTail(&factoryList, &bigBedFactory);
     slAddTail(&factoryList, &bedGraphFactory);
     slAddTail(&factoryList, &microarrayFactory);
     slAddTail(&factoryList, &coloredExonFactory);
     slAddTail(&factoryList, &encodePeakFactory);
     slAddTail(&factoryList, &bedDetailFactory);
     slAddTail(&factoryList, &adjacencyFactory);
     slAddTail(&factoryList, &bamFactory);
     slAddTail(&factoryList, &vcfTabixFactory);
     slAddTail(&factoryList, &makeItemsFactory);
     slAddTail(&factoryList, &bigDataOopsFactory);
     slAddTail(&factoryList, &barChartFactory);
     slAddTail(&factoryList, &bigBarChartFactory);
     slAddTail(&factoryList, &interactFactory);
     slAddTail(&factoryList, &bigInteractFactory);
+    slAddTail(&factoryList, &hicFactory);
     }
 }
 
 struct customFactory *customFactoryFind(char *genomeDb, struct customPp *cpp,
 	char *type, struct customTrack *track)
 /* Figure out factory that can handle this track.  The track is
  * loaded from the track line if any, and type is the type element
  * if any from that track. */
 {
 struct customFactory *fac;
 customFactoryInit();
 for (fac = factoryList; fac != NULL; fac = fac->next)
     if (fac->recognizer(fac, cpp, type, track))
 	break;
 return fac;