b76b97f1ca85ee2dbe085ead79e433bcda17a8d1
larrym
  Fri Oct 21 11:40:01 2011 -0700
prevent timeouts while uploading large custom tracks (redmine #3002)
diff --git src/hg/hgCustom/hgCustom.c src/hg/hgCustom/hgCustom.c
index c4a40c5..cd71ed5 100644
--- src/hg/hgCustom/hgCustom.c
+++ src/hg/hgCustom/hgCustom.c
@@ -6,30 +6,32 @@
 #include "cart.h"
 #include "cheapcgi.h"
 #include "web.h"
 #include "htmshell.h"
 #include "hdb.h"
 #include "hui.h"
 #include "hCommon.h"
 #include "customTrack.h"
 #include "customFactory.h"
 #include "portable.h"
 #include "errCatch.h"
 #if (defined USE_BAM && defined KNETFILE_HOOKS)
 #include "knetUdc.h"
 #include "udc.h"
 #endif//def USE_BAM && KNETFILE_HOOKS
+#include "jsHelper.h"
+#include <signal.h>
 
 static long loadTime = 0;
 
 static char const rcsid[] = "$Id: hgCustom.c,v 1.142 2010/05/28 23:04:56 kuhn Exp $";
 
 void usage()
 /* Explain usage and exit. */
 {
 errAbort(
   "hgCustom - Custom track management CGI\n"
   "usage:\n"
   "   hgCustom <CGI settings>\n"
   );
 }
 
@@ -154,33 +156,37 @@
 {
 char *dataUrl = NULL;
 char buf[1024];
 
 boolean gotClade = FALSE;
 boolean isUpdateForm = FALSE;
 if (ct)
     {
     isUpdateForm = TRUE;
     dataUrl = ctDataUrl(ct);
     }
 else
     /* add form needs clade for assembly menu */
     gotClade = hGotClade();
 
+jsIncludeFile("jquery.js", NULL);
+jsIncludeFile("hgCustom.js", NULL);
+jsIncludeFile("utils.js", NULL);
+
 /* main form */
 printf("<FORM ACTION=\"%s\" METHOD=\"%s\" "
-    " ENCTYPE=\"multipart/form-data\" NAME=\"mainForm\">\n",
+    " ENCTYPE=\"multipart/form-data\" NAME=\"mainForm\" onsubmit=\"$('input[name=Submit]').attr('disabled', 'disabled');\" >\n",
     hgCustomName(), cartUsualString(cart, "formMethod", "POST"));
 cartSaveSession(cart);
 
 if (!isUpdateForm)
     {
     /* Print clade, genome and assembly  */
     /* NOTE: this uses an additional, hidden form (orgForm), below */
     char *onChangeDb = "onchange=\"document.orgForm.db.value = document.mainForm.db.options[document.mainForm.db.selectedIndex].value; document.orgForm.submit();\"";
     char *onChangeOrg = "onchange=\"document.orgForm.org.value = document.mainForm.org.options[document.mainForm.org.selectedIndex].value; document.orgForm.db.value = 0; document.orgForm.submit();\"";
     char *onChangeClade = "onchange=\"document.orgForm.clade.value = document.mainForm.clade.options[document.mainForm.clade.selectedIndex].value; document.orgForm.org.value = 0; document.orgForm.db.value = 0; document.orgForm.submit();\"";
 
 if (hIsGsidServer())
     {
     printf("<span style='color:red;'>The Custom Track function and its documentation is currently under development ...</span><BR><BR>\n");
     }
@@ -228,31 +234,32 @@
 /* first rows for update form are for track and browser line entry */
 if (isUpdateForm)
     {
     /* row for instructions */
     cgiSimpleTableRowStart();
     cgiSimpleTableFieldStart();
     if (dataUrl)
         puts("Configuration:");
     else
         {
         puts("Edit configuration:");
         }
     cgiTableFieldEnd();
     cgiTableField("&nbsp;");
     puts("<TD ALIGN='RIGHT'>");
-    cgiMakeSubmitButton();
+    cgiMakeButtonWithOnClick("Submit", "Submit", NULL, "return submitClick(this);");
+    printf("<img id='loadingImg' src='../images/loading.gif' />\n");
     cgiTableFieldEnd();
     cgiTableField("&nbsp;");
     cgiTableRowEnd();
 
     /* row for text entry box */
     cgiSimpleTableRowStart();
     puts("<TD COLSPAN=2>");
     if (dataUrl)
         {
         /* can't update via pasting if loaded from URL */
         cgiMakeTextAreaDisableable(hgCtConfigLines,
             cartUsualString(cart, hgCtConfigLines, customTrackUserConfig(ct)),
                             CONFIG_ENTRY_ROWS, TEXT_ENTRY_COLS, TRUE);
         }
     else
@@ -293,62 +300,66 @@
 else
     cgiTableField("Paste URLs or data:");
 
 if (isUpdateForm && dataUrl)
     cgiTableField("&nbsp");
 else
     {
     puts("<TD ALIGN='RIGHT'>");
     puts("Or upload: ");
     cgiMakeFileEntry(hgCtDataFile);
     cgiTableFieldEnd();
     }
 if (!isUpdateForm)
     {
     cgiSimpleTableFieldStart();
-    cgiMakeSubmitButton();
+    cgiMakeButtonWithOnClick("Submit", "Submit", NULL, "return submitClick(this);");
+    printf("<img id='loadingImg' src='../images/loading.gif' />\n");
     cgiTableFieldEnd();
     }
 cgiTableRowEnd();
 
 /* next row - text entry box for  data, and clear button */
 cgiSimpleTableRowStart();
 puts("<TD COLSPAN=2>");
 if (dataUrl)
     {
     /* can't update via pasting if loaded from URL */
     safef(buf, sizeof buf, "Replace data at URL: %s", ctDataUrl(ct));
     cgiMakeTextAreaDisableable(hgCtDataText, buf,
                                 TEXT_ENTRY_ROWS, TEXT_ENTRY_COLS, TRUE);
     }
 else
     {
     int rows = (isUpdateForm ? TEXT_ENTRY_ROWS - CONFIG_ENTRY_ROWS :
                                 TEXT_ENTRY_ROWS);
     cgiMakeTextArea(hgCtDataText, cartUsualString(cart, hgCtDataText, ""),
                                     rows, TEXT_ENTRY_COLS);
     }
 
 cgiTableFieldEnd();
 
 cgiSimpleTableFieldStart();
 cgiSimpleTableStart();
 
 cgiSimpleTableRowStart();
 cgiSimpleTableFieldStart();
 if (!(isUpdateForm && dataUrl))
+    {
+    printf("<span id='loadingMsg'></span>\n");
     makeClearButton(hgCtDataText);
+    }
 cgiTableFieldEnd();
 cgiTableRowEnd();
 
 cgiTableEnd();
 cgiTableFieldEnd();
 cgiTableRowEnd();
 
 /* extra space */
 cgiSimpleTableRowStart();
 cgiDown(0.7);
 cgiTableRowEnd();
 
 /* next row - label for description text entry */
 cgiSimpleTableRowStart();
 cgiTableField("Optional track documentation: ");
@@ -1021,30 +1032,46 @@
 {
 struct customTrack *ct = NULL;
 for (ct = ctList; ct != NULL; ct = ct->next)
     if (sameString(track, ct->tdb->track))
         return ct;
 return NULL;
 }
 
 boolean customTrackHasConfig(char *text)
 /* determine if there are track or browser lines in text */
 {
 text = skipLeadingSpaces(text);
 return startsWith("track ", text) || startsWith("browser ", text);
 }
 
+int timerCounter;
+#define TIMER_INTERVAL 10
+
+static void timer(int sig)
+{
+// Per HTML 4.01 spec (http://www.w3.org/TR/html401/struct/global.html#h-7.1):
+//
+//      White space (spaces, newlines, tabs, and comments) may appear before or after each section [including the DOCTYPE].
+//
+// So we print out comments periodically to keep this process from being killed by apache or the user's web browser.
+
+printf("<!-- processing (%d seconds) -->\n", timerCounter++ * TIMER_INTERVAL);
+fflush(stdout);
+alarm(TIMER_INTERVAL);
+}
+
 void doMiddle(struct cart *theCart)
 /* create web page */
 {
 char *ctFileName = NULL;
 struct slName *browserLines = NULL;
 struct customTrack *replacedCts = NULL;
 char *err = NULL, *warn = NULL;
 char *selectedTable = NULL;
 struct customTrack *ct = NULL;
 boolean ctUpdated = FALSE;
 char *initialDb = NULL;
 
 long thisTime = clock1000();
 
 cart = theCart;
@@ -1102,30 +1129,40 @@
        the variable */
     selectedTable = cloneString(cartString(cart, hgCtTable));
     if (isNotEmpty(selectedTable))
         {
         ctList = customTracksParseCart(database, cart, NULL, NULL);
         ct = ctFromList(ctList, selectedTable);
         }
     if (ct)
         doUpdateCustom(ct, NULL);
     else
         doAddCustom(NULL);
     }
 else
     {
     /* get new and existing custom tracks from cart and decide what to do */
+
+    // setup a timer to periodically print out something to stdout to make sure apache or the web browser doesn't time us out (see redmine #3002).
+    // e.g. see http://stackoverflow.com/questions/5547166/how-to-avoid-cgi-timeout
+    struct sigaction *act;
+    AllocVar(act);
+    act->sa_handler = timer;
+    act->sa_flags = SA_RESTART;
+    sigaction(SIGALRM, act, NULL);
+    alarm(TIMER_INTERVAL);
+
     char *customText = fixNewData(cart);
     /* save input so we can display if there's an error */
     char *savedCustomText = saveLines(cloneString(customText),
                                 SAVED_LINE_COUNT);
     char *trackConfig = cartOptionalString(cart, hgCtConfigLines);
     char *savedConfig = cloneString(trackConfig);
 
     struct dyString *dsWarn = dyStringNew(0);
     char *fileName = cartOptionalString(cart, hgCtDataFileName);
     boolean hasData = (isNotEmpty(customText) || isNotEmpty(fileName));
     if (cartVarExists(cart, hgCtUpdatedTrack) && hasData)
         {
         /* from 'update' screen */
         /* prepend config to data for parser */
         struct dyString *dsTrack = dyStringNew(0);