71b6403f90f9f221004a07cbd4e5aecedd9f6438
galt
  Mon Apr 24 12:41:27 2017 -0700
Refs #17538. Adds ability for user to paste a custom BED region into the multi-region dialog box instead of having to provide a URL to an on-line server.  It still supports the URL through UDC however, which is more efficient for large custom region lists and also provides backwards compatibility.  Uses sha1 instead of md5 for faster speed now.

diff --git src/hg/hgTracks/hgTracks.c src/hg/hgTracks/hgTracks.c
index 9b347a2..180f028 100644
--- src/hg/hgTracks/hgTracks.c
+++ src/hg/hgTracks/hgTracks.c
@@ -58,30 +58,31 @@
 #include "agpFrag.h"
 #include "imageV2.h"
 #include "suggest.h"
 #include "search.h"
 #include "errCatch.h"
 #include "iupac.h"
 #include "botDelay.h"
 #include "chromInfo.h"
 #include "extTools.h"
 #include "basicBed.h"
 #include "customFactory.h"
 #include "genbank.h"
 #include "bigWarn.h"
 #include "wigCommon.h"
 #include "knetUdc.h"
+#include "sha1.h"
 
 /* Other than submit and Submit all these vars should start with hgt.
  * to avoid weeding things out of other program's namespaces.
  * Because the browser is a central program, most of its cart
  * variables are not hgt. qualified.  It's a good idea if other
  * program's unique variables be qualified with a prefix though. */
 char *excludeVars[] = { "submit", "Submit", "dirty", "hgt.reset",
             "hgt.in1", "hgt.in2", "hgt.in3", "hgt.inBase",
             "hgt.out1", "hgt.out2", "hgt.out3", "hgt.out4",
             "hgt.left1", "hgt.left2", "hgt.left3",
             "hgt.right1", "hgt.right2", "hgt.right3",
             "hgt.dinkLL", "hgt.dinkLR", "hgt.dinkRL", "hgt.dinkRR",
             "hgt.tui", "hgt.hideAll", "hgt.visAllFromCt",
 	    "hgt.psOutput", "hideControls", "hgt.toggleRevCmplDisp",
 	    "hgt.collapseGroups", "hgt.expandGroups", "hgt.suggest",
@@ -3898,53 +3899,197 @@
 v->start = 1 - 1;
 v->end   = 182439;
 slAddHead(&virtRegionList, v);
 
 //chr1:153865739-154045739
 AllocVar(v);
 v->chrom = "chr1";
 v->start = 153865739 - 1;
 v->end   = 154045739;
 slAddHead(&virtRegionList, v);
 
 slReverse(&virtRegionList);
 
 }
 
+void checkmultiRegionsBedInput()
+/* Check if multiRegionsBedInput needs processing.
+ * If BED submitted, see if it has changed, and if so, save it to trash
+ * and update cart and global vars. Uses sha1 hash for faster change check. */
+{
+enum custRgnType { empty, url, trashFile };
+enum custRgnType oldType = empty;
+enum custRgnType newType = empty;
+
+// OLD input
+
+char *newMultiRegionsBedUrl = NULL;
+multiRegionsBedUrl = cartUsualString(cart, "multiRegionsBedUrl", multiRegionsBedUrl);
+char multiRegionsBedUrlSha1Name[1024];
+safef(multiRegionsBedUrlSha1Name, sizeof multiRegionsBedUrlSha1Name, "%s.sha1", multiRegionsBedUrl);
+if (!multiRegionsBedUrl)
+    {
+    multiRegionsBedUrl = "";
+    cartSetString(cart, "multiRegionsBedUrl", multiRegionsBedUrl);
+    }
+if (sameString(multiRegionsBedUrl,""))
+    oldType = empty;
+else if (strstr(multiRegionsBedUrl,"://"))
+    oldType = url;
+else 
+    oldType = trashFile;
+if ((oldType == trashFile) && !(fileExists(multiRegionsBedUrl) && fileExists(multiRegionsBedUrlSha1Name)))
+    {  // if the trash files no longer exists, reset to empty string default value.
+    multiRegionsBedUrl = "";
+    cartSetString(cart, "multiRegionsBedUrl", multiRegionsBedUrl);
+    oldType = empty;
+    }
+
+// NEW input
+
+char *multiRegionsBedInput = cartOptionalString(cart, "multiRegionsBedInput");
+if (!multiRegionsBedInput)
+    return;
+
+// create cleaned up dyString from input.
+// remove blank lines, trim leading and trailing spaces, change CRLF from TEXTAREA input to LF.
+struct dyString *dyInput = dyStringNew(1024);
+char *input = cloneString(multiRegionsBedInput);  // make a copy, linefile modifies
+struct lineFile *lf = lineFileOnString("multiRegionsBedInput", TRUE, input);
+char *line;
+int lineSize;
+while (lineFileNext(lf, &line, &lineSize))
+    {
+    line = trimSpaces(line);
+    if (sameString(line, "")) // skip blank lines
+	continue;
+    dyStringAppend(dyInput,line);
+    dyStringAppend(dyInput,"\n");
+    }
+lineFileClose(&lf);
+
+// test multiRegionsBedInput. empty? url? trashFile?
+input = cloneString(dyInput->string);  // make a copy, linefile modifies
+lf = lineFileOnString("multiRegionsBedInput", TRUE, input);
+int lineCount = 0;
+while (lineFileNext(lf, &line, &lineSize))
+    {
+    ++lineCount;
+    if (lineCount==1 && 
+	(startsWithNoCase("http://" ,line)
+      || startsWithNoCase("https://",line)
+      || startsWithNoCase("ftp://"  ,line)))
+	{
+	// new value is a URL. set vars and cart.
+	newMultiRegionsBedUrl = cloneString(line);
+	newType = url;
+	}
+    break;
+    }
+lineFileClose(&lf);
+if (newType != url)
+    {
+    if (lineCount == 0)  // there are no non-blank lines
+	{	
+	newMultiRegionsBedUrl = "";
+	newType = empty;
+	}
+    else
+	newType = trashFile;
+    }
+
+char *newSha1 = NULL;
+if (newType==trashFile)
+    {
+    // calculate sha1 checksum on new input.
+    newSha1 = sha1HexForString(dyInput->string);
+    }
+
+// compare input sha1 to trashFile sha1 to see if same
+boolean filesAreSame = FALSE;
+if (oldType==trashFile && newType==trashFile)
+    {
+    lf = lineFileMayOpen(multiRegionsBedUrlSha1Name, TRUE);
+    while (lineFileNext(lf, &line, &lineSize))
+	{
+	if (sameString(line, newSha1))
+	    filesAreSame = TRUE;
+	}
+    lineFileClose(&lf);
+    }
+
+// save new trashFile unless no changes.
+if (newType==trashFile && (!(oldType==trashFile && filesAreSame) ))
+    {
+    struct tempName bedTn;
+    trashDirFile(&bedTn, "hgt", "custRgn", ".bed");
+    FILE *f = mustOpen(bedTn.forCgi, "w");
+    mustWrite(f, dyInput->string, dyInput->stringSize);
+    carefulClose(&f);
+    // new value is a trash file. 
+    newMultiRegionsBedUrl = cloneString(bedTn.forCgi);
+    // save new input sha1 to trash file.
+    safef(multiRegionsBedUrlSha1Name, sizeof multiRegionsBedUrlSha1Name, "%s.sha1", bedTn.forCgi);
+    f = mustOpen(multiRegionsBedUrlSha1Name, "w");
+    mustWrite(f, newSha1, strlen(newSha1));
+    carefulClose(&f);
+    }
+
+dyStringFree(&dyInput);
+
+// if new value, set vars and cart
+if (newMultiRegionsBedUrl)
+    {
+    multiRegionsBedUrl = newMultiRegionsBedUrl;
+    cartSetString(cart, "multiRegionsBedUrl", multiRegionsBedUrl);
+    }
+cartRemove(cart, "multiRegionsBedInput");
+}
+
+
 boolean initVirtRegionsFromBedUrl(time_t *bedDateTime)
 /* Read custom regions from BED URL */
 {
 multiRegionsBedUrl = cartUsualString(cart, "multiRegionsBedUrl", multiRegionsBedUrl);
 int bedPadding = 0; // default no padding
-// TODO add some checks for db change? save in cart var?
 if (sameString(multiRegionsBedUrl,""))
     {
-    warn("No BED URL specified.");
+    warn("No BED or BED URL specified.");
     return FALSE;
     }
-if (!strstr(multiRegionsBedUrl,"://"))
+
+struct lineFile *lf = NULL;
+if (strstr(multiRegionsBedUrl,"://"))
     {
-    warn("No protocol specified in BED URL %s", multiRegionsBedUrl);
-    return FALSE;
-    }
-struct lineFile *lf = lineFileUdcMayOpen(multiRegionsBedUrl, FALSE);
+    lf = lineFileUdcMayOpen(multiRegionsBedUrl, FALSE);
     if (!lf)
 	{
 	warn("Unable to open [%s] with udc", multiRegionsBedUrl);
 	return FALSE;
 	}
     *bedDateTime = udcTimeFromCache(multiRegionsBedUrl, NULL);
+    }
+else
+    {
+    lf = lineFileMayOpen(multiRegionsBedUrl, TRUE);
+    if (!lf)
+	{
+	warn("BED custom regions file [%s] not found.", multiRegionsBedUrl);
+	return FALSE;
+	}
+    *bedDateTime = 0;
+    }
 char *line;
 int lineSize;
 int expectedFieldCount = -1;
 struct bed *bed, *bedList = NULL;
 while (lineFileNext(lf, &line, &lineSize))
     {
     // Process comments for keywords like database, shortDesc, and maybe others
     if (startsWith("#",line))
 	{
 	if (startsWith("#database ",line))
 	    {
 	    char *dbFromBed = line+strlen("#database ");
 	    if (!sameString(database,dbFromBed))
 		{
 		warn("Multi-Region BED URL error: The database (%s) specified in input does not match current database %s", 
@@ -4066,30 +4211,32 @@
 void dySaveCartSetting(struct dyString *dy, char *cartVar, boolean saveBoth)
 /* Grab var and value from cart, save as var=val to dy string. */
 {
 if (dy->stringSize > 0)
     dyStringAppend(dy, " ");
 dyStringPrintf(dy, "%s=%s", cartVar, cartUsualString(cart, cartVar, NULL));
 if (saveBoth)
     lastDbPosSaveCartSetting(cartVar);
 }
 
 
 boolean initRegionList()
 /* initialize window list */
 {
 
+checkmultiRegionsBedInput();
+
 struct virtRegion *v;
 virtRegionList = NULL;
 virtModeExtraState = "";   // This is state that determines if the virtChrom has changed
 lastDbPosCart = cartOfNothing();  // USED to store and restore cart settings related to position and virtMode
 struct dyString *dy = dyStringNew(256);  // used to build virtModeExtraState
 
 if (sameString(virtModeType, "default"))
     { 
     // Single window same as normal window
     // mostly good to test nothing was broken with single window
     AllocVar(v);
 
     v->chrom = chromName;
     v->start = 0;
     v->end = hChromSize(database, chromName);