c79deb301511fcf0de0ed376c7746e4902804472 chmalee Fri May 5 16:59:33 2023 -0700 Experiment number one, can succesfully upload a file and store it via hgCustom. New userdata library for managing where to store the files Get skeleton structure of new cgi together More work in progress, mostly stubbing out the CGI More work in progress, mostly stubbing out the html page diff --git src/hg/hgCustom/hgCustom.c src/hg/hgCustom/hgCustom.c index 2616c8a..a427a2e 100644 --- src/hg/hgCustom/hgCustom.c +++ src/hg/hgCustom/hgCustom.c @@ -1,1447 +1,1447 @@ /* hgCustom - Custom track management CGI. */ /* Copyright (C) 2014 The Regents of the University of California * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */ #include "common.h" #include "obscure.h" #include "linefile.h" #include "hash.h" #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" #include "knetUdc.h" #include "udc.h" #include "net.h" #include "jsHelper.h" #include <signal.h> #include "trackHub.h" #include "botDelay.h" #include "chromAlias.h" static long loadTime = 0; static boolean issueBotWarning = FALSE; #define delayFraction 0.25 /* same as hgTracks */ void usage() /* Explain usage and exit. */ { errAbort( "hgCustom - Custom track management CGI\n" "usage:\n" " hgCustom <CGI settings>\n" ); } /* DON'T EDIT THIS -- use CGI param "&measureTiming=." */ static boolean measureTiming = FALSE; #define TEXT_ENTRY_ROWS 7 #define TEXT_ENTRY_COLS 73 #define CONFIG_ENTRY_ROWS 3 #define SAVED_LINE_COUNT 50 /* CGI variables */ #define hgCt "hgct_" /* prefix for all control variables; these are removed at end! */ #define hgCtNoRemove "hgctNoRemove_" /* these are shared with other modules */ #define hgCtDataText CT_CUSTOM_TEXT_ALT_VAR #define hgCtDataFile CT_CUSTOM_FILE_VAR #define hgCtDataFileName CT_CUSTOM_FILE_NAME_VAR #define hgCtDocText CT_CUSTOM_DOC_TEXT_VAR #define hgCtDocFile CT_CUSTOM_DOC_FILE_VAR #define hgCtTable CT_SELECTED_TABLE_VAR #define hgCtUpdatedTable CT_UPDATED_TABLE_VAR /* misc */ #define hgCtUpdatedTrack "hgct_updatedTrack" #define hgCtDeletePrefix "hgct_del" #define hgCtRefreshPrefix "hgct_refresh" #define hgCtConfigLines "hgct_configLines" #define hgCtNavDest hgCtNoRemove "navDest" /* commands */ #define hgCtDo hgCt "do_" /* prefix for all commands */ #define hgCtDoAdd hgCtDo "add" #define hgCtDoDelete hgCtDo "delete" #define hgCtDoDeleteSet hgCtDo "delete_set" #define hgCtDoDeleteClr hgCtDo "delete_clr" #define hgCtDoRefresh hgCtDo "refresh" #define hgCtDoRefreshSet hgCtDo "refresh_set" #define hgCtDoRefreshClr hgCtDo "refresh_clr" #define hgCtDoGenomeBrowser hgCtDo "gb" #define hgCtDoTableBrowser hgCtDo "tb" #ifdef PROGRESS_METER #define hgCtDoProgress hgCtDo "progress" #endif /* Global variables */ struct cart *cart; struct hash *oldVars = NULL; char *excludeVars[] = {"Submit", "submit", "SubmitFile", "ContinueWithWarn", NULL}; char *database = NULL; char *organism = NULL; struct customTrack *ctList = NULL; void makeClearButton(char *field) /* UI button that clears a text field */ { char id[256]; safef(id, sizeof id, "%s_clear", field); char javascript[1024]; safef(javascript, sizeof javascript, "document.mainForm.%s.value = '';", field); cgiMakeOnClickButton(id, javascript, " Clear "); } void addIntro() /* display overview and help message for "add" screen */ { puts(" Data must be formatted in\n" " <A TARGET=_BLANK HREF='../goldenPath/help/bigBed.html'>bigBed</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/barChart.html'>bigBarChart</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/bigChain.html'>bigChain</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/bigGenePred.html'>bigGenePred</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/interact.html'>bigInteract</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/bigLolly.html'>bigLolly</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/bigMaf.html'>bigMaf</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/bigPsl.html'>bigPsl</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/bigWig.html'>bigWig</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/bam.html'>BAM</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/barChart.html'>barChart</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/vcf.html'>VCF</A>,\n" " <A TARGET=_BLANK HREF='../FAQ/FAQformat.html#format1'>BED</A>,\n" " <A TARGET=_BLANK HREF='../FAQ/FAQformat.html#format1.7'>BED detail</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/bedgraph.html'>bedGraph</A>,\n" " <A TARGET=_BLANK HREF='../FAQ/FAQformat.html#format13'>broadPeak</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/cram.html'>CRAM</A>,\n" " <A TARGET=_BLANK HREF='../FAQ/FAQformat.html#format3'>GFF</A>,\n" " <A TARGET=_BLANK HREF='../FAQ/FAQformat.html#format4'>GTF</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/hic.html'>hic</A>,\n" " <A TARGET=_BLANK HREF='../goldenPath/help/interact.html'>interact</A>,\n" " <A TARGET=_BLANK HREF='../FAQ/FAQformat.html#format5'>MAF</A>,\n" " <A TARGET=_BLANK HREF='../FAQ/FAQformat.html#format12'>narrowPeak</A>,\n" " <A TARGET=_BLANK HREF='../FAQ/FAQformat.html#format10'>Personal Genome SNP,</A>\n" " <A TARGET=_BLANK HREF='../FAQ/FAQformat.html#format2'>PSL</A>,\n" " or <A TARGET=_BLANK HREF='../goldenPath/help/wiggle.html'>WIG</A>\n" " formats.<br>\n" " <ul>\n" " <li>You can paste just the URL to the file, without a \"track\" line, for bigBed, bigWig, bigGenePred, CRAM, BAM and VCF.<br></li>" " <li>To configure the display, set\n" " <A TARGET=_BLANK HREF='../goldenPath/help/customTrack.html#TRACK'>track</A>\n" " and" " <A TARGET=_BLANK HREF='../goldenPath/help/customTrack.html#BROWSER'>browser</A>\n" " line attributes as described in the \n" " <A TARGET=_BLANK HREF='../goldenPath/help/customTrack.html'>User's Guide</A>.<br>\n" " Examples are\n" " <A TARGET=_BLANK HREF='../goldenPath/help/customTrack.html#EXAMPLE1'>here</A>.\n" " If you do not have web-accessible data storage available, please see the\n" " <A TARGET=_BLANK HREF='../goldenPath/help/hgTrackHubHelp.html#Hosting'>Hosting</A> section of the Track Hub Help documentation.\n<br><br></li>" " </ul>\n" " Please note a much more efficient way to load data is to use\n" " <A TARGET=_BLANK HREF='../goldenPath/help/hgTrackHubHelp.html'>Track Hubs</A>, which are loaded\n" " from the <A TARGET=_BLANK HREF='hgHubConnect'>Track Hubs Portal</A> found in the menu under My Data.\n" ); } void addCustomForm(struct customTrack *ct, char *err, boolean warnOnly) /* display UI for adding custom tracks by URL or pasting data */ { 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); jsIncludeFile("ajax.js", NULL); /* main form */ printf("<FORM ACTION=\"%s\" METHOD=\"%s\" " " ENCTYPE=\"multipart/form-data\" NAME=\"mainForm\" id='mainForm'>\n", hgCustomName(), cartUsualString(cart, "formMethod", "POST")); jsOnEventById("submit", "mainForm", "$('input[name=Submit]').attr('disabled', 'disabled');"); cartSaveSession(cart); if (!isUpdateForm) { /* Print clade, genome and assembly */ /* NOTE: this uses an additional, hidden form (orgForm), below */ char *onChangeDb = "document.orgForm.db.value = document.mainForm.db.options[document.mainForm.db.selectedIndex].value; document.orgForm.submit();"; char *onChangeOrg = "document.orgForm.org.value = document.mainForm.org.options[document.mainForm.org.selectedIndex].value; document.orgForm.db.value = 0; document.orgForm.submit();"; char *onChangeClade = "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();"; puts("<TABLE BORDER=0>\n"); if (gotClade) { puts("<TR><TD>Clade\n"); printCladeListHtml(hOrganism(database), "change", onChangeClade); puts(" "); puts("Genome\n"); printGenomeListForCladeHtml(database, "change", onChangeOrg); } else { puts("<TR><TD>genome\n"); printGenomeListHtml(database, "change", onChangeOrg); } puts(" "); puts("Assembly\n"); printAssemblyListHtml(database, "change", onChangeDb); char *description = hFreezeFromDb(database); if ((description != NULL) && ! stringIn(database, description)) { puts(" "); printf("[%s]", trackHubSkipHubName(database)); } puts("</TD></TR></TABLE>\n"); } /* intro text */ puts("<P>"); if (isUpdateForm) puts("Update your custom track configuration, data, and/or documentation."); else puts("Display your own data as custom annotation tracks in the browser."); addIntro(); puts("<P>"); /* row for error message */ if (isNotEmpty(err)) { printf("<P><B> <span style='color:%s; font-style:italic;'>" "%s</span><P>%s</B><P>", warnOnly ? "ORANGE" : "RED", warnOnly ? "Warning" : "Error", htmlEncode(err)); /* send two lines of the message to the apache error log also: */ char *tmpString = replaceChars(err, "\n", " "); fprintf(stderr, "hgCustom load error: %s\n", tmpString); freeMem(tmpString); } cgiSimpleTableStart(); /* 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(" "); puts("<TD ALIGN='RIGHT'>"); if (warnOnly) { cgiMakeButtonWithOnClick("ContinueWithWarn", "Continue with Warning", NULL, "return submitClick(this);"); printf(" "); jsInline( "$('textarea').change(function() {\n" " $('#ContinueWithWarn').hide();\n" "});\n"); } cgiMakeButtonWithOnClick("Submit", "Submit", NULL, "return submitClick(this);"); printf("<img id='loadingImg' src='../images/loading.gif' />\n"); cgiTableFieldEnd(); cgiTableField(" "); 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 { cgiMakeTextArea(hgCtConfigLines, cartUsualString(cart, hgCtConfigLines, customTrackUserConfig(ct)), CONFIG_ENTRY_ROWS, TEXT_ENTRY_COLS); } cgiTableFieldEnd(); cgiSimpleTableFieldStart(); cgiSimpleTableStart(); cgiSimpleTableRowStart(); cgiSimpleTableFieldStart(); cgiTableFieldEnd(); cgiTableRowEnd(); cgiTableEnd(); cgiTableFieldEnd(); cgiTableRowEnd(); } /* next row - label entry for file upload */ cgiSimpleTableRowStart(); if (isUpdateForm) { /* update existing */ /* extra space */ cgiSimpleTableRowStart(); puts("<TD STYLE='padding-top:9';\"></TD>"); cgiTableRowEnd(); if (dataUrl) cgiTableField("Data:"); else cgiTableField("Paste in replacement data:"); } else cgiTableField("Paste URLs or data:"); if (isUpdateForm && dataUrl) cgiTableField(" "); else { puts("<TD ALIGN='RIGHT'>"); puts("Or upload: "); cgiMakeFileEntry(hgCtDataFile); cgiTableFieldEnd(); jsInline( "$(\"[name='hgt.customFile']\").change(function(ev) { \n" " var fname = ev.target.files[0].name; \n" " var ext = fname.split('.').pop().toLowerCase(); \n" - " var warnExts = ['bigbed', 'bb', 'bam', 'bigwig', 'bw', 'jpeg', 'pdf', 'jpg', 'png', 'hic', 'cram'];\n" + " var warnExts = ['jpeg', 'pdf', 'jpg', 'png', 'mov', 'webm', 'gif', 'webp', 'img', 'jar', 'rar', 'tar', 'html', 'md', 'doc', 'docx', 'xlsx', 'xls', 'rtf', 'xml', 'svg'];\n" " if (warnExts.indexOf(ext) >= 0) {\n" - " alert('You are trying to upload a binary file on this page, but the Genome Browser server needs access to binary files via the internet.'+" + " alert('You are trying to upload an unsupported file on this page, but the Genome Browser server needs access to binary files via the internet.'+" " ' Therefore, you will need to store the files on a web server, then paste the URLs to them on this page, or upload a text file with \"track\" lines '+" " ' and configuration settings that point to the file URLs. Please read the documentation'+" " ' referenced at the top of this page or contact us for more information.');\n" " $(\"[name='hgt.customFile']\")[0].value = '';" " }\n" "});\n" ); } if (!isUpdateForm) { cgiSimpleTableFieldStart(); if (warnOnly) { cgiMakeButtonWithOnClick("ContinueWithWarn", "Continue with Warning", NULL, "return submitClick(this);"); printf(" "); jsInline( "$('textarea').change(function() {\n" " $('#ContinueWithWarn').hide();\n" "});\n"); } 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, "Data was uploaded from URL: %s\nTo see changes made to the data at the URL, re-load the URL on the \"Add custom tracks\" page.", 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(); cgiSimpleTableFieldStart(); cgiDown(0.7); cgiTableFieldEnd(); cgiTableRowEnd(); /* next row - label for description text entry */ cgiSimpleTableRowStart(); cgiTableField("Optional track documentation: "); if (isUpdateForm && ctHtmlUrl(ct)) cgiTableField(" "); else { puts("<TD ALIGN='RIGHT'>"); puts("Or upload: "); cgiMakeFileEntry(hgCtDocFile); cgiTableFieldEnd(); } cgiTableRowEnd(); /* next row - text entry for description, and clear button(s) */ cgiSimpleTableRowStart(); puts("<TD COLSPAN=2>"); if (ct && ctHtmlUrl(ct)) { safef(buf, sizeof buf, "Replace doc at URL: %s", dataUrl); cgiMakeTextAreaDisableable(hgCtDocText, buf, TEXT_ENTRY_ROWS, TEXT_ENTRY_COLS, TRUE); } else { cgiMakeTextArea(hgCtDocText, cartUsualString(cart, hgCtDocText, ""), TEXT_ENTRY_ROWS, TEXT_ENTRY_COLS); cgiTableFieldEnd(); cgiSimpleTableFieldStart(); cgiSimpleTableStart(); cgiSimpleTableRowStart(); cgiSimpleTableFieldStart(); makeClearButton(hgCtDocText); cgiTableFieldEnd(); cgiTableRowEnd(); cgiTableEnd(); } cgiTableFieldEnd(); cgiTableRowEnd(); cgiTableEnd(); /* help text at bottom of screen - link for HTML description template */ puts("Click <A HREF=\"../goldenPath/help/ct_description.txt\" TARGET=_blank>here</A> for an HTML document template that may be used for Genome Browser track descriptions."); if (isUpdateForm) { /* hidden variables to identify track */ cgiMakeHiddenVar(hgCtUpdatedTable, ct->tdb->track); char buf[512]; char *shortLabel = htmlEncode(ct->tdb->shortLabel); char *longLabel = htmlEncode(ct->tdb->longLabel); safef(buf, sizeof buf, "track name='%s' description='%s'", shortLabel, longLabel); char *trackLine = htmlEncode(ctOrigTrackLine(ct)); cgiMakeHiddenVar(hgCtUpdatedTrack, trackLine ? trackLine : buf); freeMem(trackLine); freeMem(shortLabel); freeMem(longLabel); } else { /* hidden form to handle clade/genome/assembly dropdown. * This is at end of page for layout reasons (preserve vertical space) */ puts("</FORM>"); printf("<FORM STYLE=\"margin-bottom:0;\" ACTION=\"%s\" METHOD=\"GET\" NAME=\"orgForm\">", hgCustomName()); cartSaveSession(cart); if (gotClade) printf("<INPUT TYPE=\"HIDDEN\" NAME=\"Clade\" VALUE=\"\">\n"); printf("<INPUT TYPE=\"HIDDEN\" NAME=\"org\" VALUE=\"%s\">\n", organism); printf("<INPUT TYPE=\"HIDDEN\" NAME=\"db\" VALUE=\"%s\">\n", database); printf("<INPUT TYPE=\"HIDDEN\" NAME=\"hgct_do_add\" VALUE=\"1\">\n"); } puts("</FORM>"); cgiDown(0.9); } void tableHeaderFieldStart(int columns) { /* print table column header with white text on black background */ printf("<TD COLSPAN=%d ALIGN='CENTER' BGCOLOR='#536ED3'>", columns); } void tableHeaderField(char *label, char *description) { /* print table column header with white text on black background */ puts("<TD ALIGN='CENTER' BGCOLOR='#536ED3' "); if (description) printf("TITLE='%s'", description); printf("><B>%s</B></TD> ", wrapWhiteFont(label)); } void showCustomTrackList(struct customTrack *ctList, int numCts) /* print table of custom tracks with controls */ { struct customTrack *ct; char buf[256]; char *pos = NULL; char *dataUrl; int colSpan = 4; /* handle 'set all' and 'clr all' (won't be used if user has javascript enabled). */ boolean setAllDelete = FALSE; boolean setAllUpdate = FALSE; if (cartVarExists(cart, hgCtDoDeleteSet)) setAllDelete = TRUE; if (cartVarExists(cart, hgCtDoRefreshSet)) setAllUpdate = TRUE; /* determine which columns to display (avoid empty columns) */ int updateCt = 0, itemCt = 0, posCt = 0, errCt = 0; for (ct = ctList; ct != NULL; ct = ct->next) { if (ctDataUrl(ct)) updateCt++; if (ctItemCount(ct) > 0) itemCt++; if (ctInitialPosition(ct) || ctFirstItemPos(ct)) posCt++; if (ct->networkErrMsg) errCt++; } hTableStart(); cgiSimpleTableRowStart(); tableHeaderField("Name", "Short track identifier"); tableHeaderField("Description", "Long track identifier"); tableHeaderField("Type", "Data format of track"); tableHeaderField("Doc", "HTML track description"); if (itemCt) { tableHeaderField("Items", "Count of discrete items in track"); colSpan++; } if (posCt) { tableHeaderField("Pos"," Go to genome browser at default track position or first item"); colSpan++; } if (errCt) { tableHeaderField("Error"," Error in custom track"); colSpan++; } boolean showAllButtons = FALSE; if (numCts > 3) showAllButtons = TRUE; tableHeaderFieldStart(showAllButtons ? 2 : 1); cgiMakeButtonWithMsg(hgCtDoDelete, "Delete", "Remove custom track"); cgiTableFieldEnd(); /* add column with Update button if any custom tracks are updateable */ if (updateCt) { tableHeaderFieldStart(showAllButtons ? 2 : 1); cgiMakeButtonWithMsg(hgCtDoRefresh, "Update", "Refresh from data URL"); cgiTableFieldEnd(); } cgiTableRowEnd(); int butCount=0; for (ct = ctList; ct != NULL; ct = ct->next) { /* Name field */ char *shortLabel = htmlEncode(ct->tdb->shortLabel); if ((ctDataUrl(ct) && ctHtmlUrl(ct)) || sameString(ct->tdb->type, "chromGraph")) printf("<TR><TD>%s</A></TD>", shortLabel); else { char *cgiName = cgiEncode(ct->tdb->track); printf("<TR><TD><A TITLE='Update custom track: %s' HREF='%s?%s&%s=%s'>%s</A></TD>", shortLabel, hgCustomName(),cartSidUrlString(cart), hgCtTable, cgiName, shortLabel); freeMem(cgiName); } freeMem(shortLabel); /* Description field */ char *longLabel = htmlEncode(ct->tdb->longLabel); printf("<TD>%s</TD>", longLabel); freeMem(longLabel); /* Type field */ printf("<TD>%s</TD>", ctInputType(ct)); /* Doc field */ printf("<TD ALIGN='CENTER'>%s</TD>", isNotEmpty(ct->tdb->html) ? "Y" : " "); /* Items field */ if (itemCt) { int count = ctItemCount(ct); if (count > 0) printf("<TD ALIGN='CENTER'>%d</TD>", count); else puts("<TD> </TD>"); } /* Pos field; indicates initial position for the track, * or first element */ if (posCt) { pos = ctInitialPosition(ct); if (!pos) pos = ctFirstItemPos(ct); if (pos) { char *chrom = cloneString(pos); chopSuffixAt(chrom, ':'); if (hgOfficialChromName(database, chrom)) printf("<TD><A HREF='%s?%s&position=%s&hgTracksConfigPage=notSet' TITLE=%s>%s:</A></TD>", hgTracksName(), cartSidUrlString(cart),pos, pos, chrom); else puts("<TD> </TD>"); } else puts("<TD> </TD>"); } if (errCt) { if (ct->networkErrMsg) { char id[256]; safef(id, sizeof id, "_%d", butCount); printf("\n<TD><A href='#' id='%s'>Show</A></TD>\n", id); jsOnEventByIdF("click", id, "alert('%s');return false;", javaScriptLiteralEncode(ct->networkErrMsg)); } else puts("<TD> </TD>"); } /* Delete checkboxes */ printf("<TD COLSPAN=%d ALIGN=CENTER>", showAllButtons ? 2 : 1); safef(buf, sizeof(buf), "%s_%s", hgCtDeletePrefix, ct->tdb->track); cgiMakeCheckBoxMore(buf, setAllDelete, "class='deleteCheckbox'"); puts("</TD>"); /* Update checkboxes */ if (updateCt) { printf("<TD COLSPAN=%d ALIGN=CENTER>", showAllButtons ? 2 : 1); safef(buf, sizeof(buf), "%s_%s", hgCtRefreshPrefix, ct->tdb->track); if ((dataUrl = ctDataUrl(ct)) != NULL) { char more[2048]; safef(more, sizeof(more), "class='updateCheckbox' title='refresh data from: %s'", dataUrl); cgiMakeCheckBoxMore(buf, setAllUpdate, more); } else puts(" "); puts("</TD>"); } puts("</TR>\n"); } if (showAllButtons) { cgiSimpleTableRowStart(); printf("<TD COLSPAN=%d ALIGN='RIGHT'>check all / clear all </TD>", colSpan); cgiSimpleTableFieldStart(); cgiMakeButtonWithOnClick(hgCtDoDeleteSet, "+", "Select all for deletion", "$('.deleteCheckbox').attr('checked', true); return false;"); cgiTableFieldEnd(); cgiSimpleTableFieldStart(); cgiMakeButtonWithOnClick(hgCtDoDeleteClr, "-", "Clear all for deletion", "$('.deleteCheckbox').attr('checked', false); return false;"); cgiTableFieldEnd(); if (updateCt) { cgiSimpleTableFieldStart(); cgiMakeButtonWithOnClick(hgCtDoRefreshSet, "+", "Select all for update", "$('.updateCheckbox').attr('checked', true); return false;"); cgiTableFieldEnd(); cgiSimpleTableFieldStart(); cgiMakeButtonWithOnClick(hgCtDoRefreshClr, "-", "Clear all for update", "$('.updateCheckbox').attr('checked', false); return false;"); cgiTableFieldEnd(); } cgiTableRowEnd(); } hTableEnd(); } struct dbDb *getCustomTrackDatabases() /* Get list of databases having custom tracks for this user. * Dispose of this with dbDbFreeList. */ { struct dbDb *dbList = NULL, *dbDb; char *db; /* Get list of assemblies with custom tracks */ struct hashEl *hels = cartFindPrefix(cart, CT_FILE_VAR_PREFIX); struct hashEl *hel = NULL; for (hel = hels; hel != NULL; hel = hel->next) { /* TODO: chop actual prefix */ db = chopPrefixAt(cloneString(hel->name), '_'); /* TODO: check if file exists, if not remove ctfile_ var */ dbDb = hDbDb(db); if (dbDb) slAddTail(&dbList, dbDb); } return dbList; } static struct slPair *makeOtherCgiValsAndLabels() /* Return {value, label} pairs with other CGIs the user might wish to jump to. */ { struct slPair *valsAndLabels = slPairNew(hgTracksName(), "Genome Browser"); slAddHead(&valsAndLabels, slPairNew(hgTablesName(), "Table Browser")); slAddHead(&valsAndLabels, slPairNew(hgVaiName(), "Variant Annotation Integrator")); slAddHead(&valsAndLabels, slPairNew(hgIntegratorName(), "Data Integrator")); slReverse(&valsAndLabels); return valsAndLabels; } static void makeOtherCgiForm(char *pos) /* Make a form for navigating to other CGIs. */ { struct slPair *valsAndLabels = makeOtherCgiValsAndLabels(); // Default to the first CGI in the menu. char *defaultCgi = valsAndLabels->name; char *selected = cartUsualString(cart, hgCtNavDest, defaultCgi); printf("<FORM STYLE=\"margin-bottom:0;\" METHOD=\"GET\" NAME=\"navForm\" ID=\"navForm\"" " ACTION=\"%s\">\n", selected); cartSaveSession(cart); if (pos) cgiMakeHiddenVar("position", pos); printf("view in "); // Construct a menu of destination CGIs puts(cgiMakeSingleSelectDropList(hgCtNavDest, valsAndLabels, selected, NULL, NULL, "change", "var newVal = $('#navSelect').val(); $('#navForm').attr('action', newVal);", NULL, "navSelect")); cgiMakeButton("submit", "Go to first annotation"); puts(" <input type='submit' name='submit' id='submitGoBack' value='Return to current position'>"); jsOnEventByIdF("click", "submitGoBack", "$('#navForm > [name=position]').remove()"); puts("</FORM>"); } static void manageCustomForm(char *warnMsg) /* list custom tracks and display checkboxes so user can select for delete */ { struct dbDb *dbList = getCustomTrackDatabases(); struct dbDb *dbDb = NULL; /* add this database to the list, as it may have no custom * tracks, but we still want to see it in the menu */ slAddTail(&dbList, hDbDb(database)); slReverse(&dbList); /* remove duplicate entry for this database, if any */ for (dbDb = dbList->next; dbDb != NULL; dbDb = dbDb->next) if (sameString(dbDb->name, database)) slRemoveEl(&dbList, dbDb); boolean assemblyMenu = FALSE; if (slCount(dbList) > 1) assemblyMenu = TRUE; if (assemblyMenu) { /* hidden form to handle genome/assembly dropdown */ printf("<FORM STYLE=\"margin-bottom:0;\" ACTION=\"%s\" METHOD=\"GET\" NAME=\"orgForm\">", hgCustomName()); cartSaveSession(cart); printf("<INPUT TYPE=\"HIDDEN\" NAME=\"org\" VALUE=\"%s\">\n", organism); printf("<INPUT TYPE=\"HIDDEN\" NAME=\"db\" VALUE=\"%s\">\n", database); puts("</FORM>"); } /* the main form contains a table of all tracks, with checkboxes to delete */ printf("<FORM ACTION=\"%s\" METHOD=\"%s\" NAME=\"mainForm\">\n", hgCustomName(), cartUsualString(cart, "formMethod", "POST")); cartSaveSession(cart); if (assemblyMenu) { /* Print clade, genome and assembly */ char *onChangeDb = "document.orgForm.db.value = document.mainForm.db.options[document.mainForm.db.selectedIndex].value; document.orgForm.submit();"; char *onChangeOrg = "document.orgForm.org.value = document.mainForm.org.options[document.mainForm.org.selectedIndex].value; document.orgForm.db.value = 0; document.orgForm.submit();"; puts("<TABLE BORDER=0>\n"); puts("<TR><TD>genome\n"); printSomeGenomeListHtml(database, dbList, "change", onChangeOrg); puts(" "); puts("assembly\n"); printSomeAssemblyListHtml(database, dbList, "change", onChangeDb); puts(" "); printf("[%s]", database); puts("</TD></TR></TABLE><P>\n"); } else { char *assemblyName = hFreezeDateOpt(database); if (assemblyName == NULL) assemblyName = "default"; printf("<B>Genome:</B> %s <B>Assembly:</B> %s [%s]\n", organism, assemblyName, database); } if (measureTiming && (loadTime > 0)) printf("\n<BR>load time: %ld ms<BR>\n", loadTime); /* place for warning messages to appear */ if (isNotEmpty(warnMsg)) { char *encoded = htmlEncode(warnMsg); printf("<P><B> %s", encoded); freeMem(encoded); } /* count up number of custom tracks for this genome */ int numCts = slCount(ctList); cgiSimpleTableStart(); cgiSimpleTableRowStart(); if (numCts) { puts("<TD VALIGN=\"TOP\">"); showCustomTrackList(ctList, numCts); } else puts("<TD VALIGN=\"TOP\"><B><EM>No custom tracks for this genome:<B></EM> "); puts("</TD>"); /* end mainForm; navigation menu has its own form. */ puts("</FORM>"); puts("<TD VALIGN=\"TOP\">"); puts("<TABLE BORDER=0>"); /* determine if there's a navigation position for this screen */ char *pos = NULL; if (ctList) { pos = ctInitialPosition(ctList); if (!pos) pos = ctFirstItemPos(ctList); } puts("<TR><TD>"); makeOtherCgiForm(pos); puts("</TD></TR>"); /* button to add custom tracks */ puts("<TR><TD>"); printf("<INPUT TYPE=SUBMIT NAME=\"addTracksButton\" ID=\"addTracksButton\" VALUE=\"%s\" " "STYLE=\"margin-top: 5px\" >\n", "Add custom tracks"); // This submits mainForm with a hidden input that tells hgCustom to show add tracks page: jsOnEventByIdF("click", "addTracksButton", "var $form = $(\"form[name='mainForm']\"); " "$form.append(\"<input name='%s' type='hidden'>\"); " "$form.submit();" , hgCtDoAdd); puts("</TD></TR>"); puts("</TABLE>"); puts("</TD>"); cgiTableRowEnd(); cgiTableEnd(); // This vertically aligns the 'add tracks' button with the other-CGI select jsInline( "function fitUnder($el1, $el2) { " " var off1 = $el1.offset(); " " var off2 = $el2.offset(); " " off2.left = off1.left; " " $el2.offset(off2);" " $el2.width($el1.width()); " "};" "$(document).ready(function () { fitUnder($('#navSelect'), $('#addTracksButton')); });\n" ); cartSetString(cart, "hgta_group", "user"); } void helpCustom() /* display documentation */ { webNewSection("Loading Custom Tracks"); char *browserVersion; if (btIE == cgiClientBrowser(&browserVersion, NULL, NULL) && *browserVersion < '8') puts("<span>"); else puts("<span style='position:relative; top:-1em;'>"); webIncludeHelpFile("customTrackLoad", FALSE); puts("</span>"); } void doBrowserLines(struct slName *browserLines, char **retErr) /* parse variables from browser lines into the cart */ { char *err = NULL; struct slName *bl; for (bl = browserLines; bl != NULL; bl = bl->next) { char *words[96]; int wordCount; wordCount = chopLine(bl->name, words); if (wordCount > 1) { char *command = words[1]; if (sameString(command, "hide") || sameString(command, "dense") || sameString(command, "pack") || sameString(command, "squish") || sameString(command, "full")) { if (wordCount > 2) { int i; for (i=2; i<wordCount; ++i) { char *s = words[i]; if (sameWord(s, "all")) { cartSetString(cart, "hgt.visAllFromCt", command); } else { char buf[256]; safef(buf, sizeof buf, "hgtct.%s", s); cartSetString(cart, buf, command); } } } } else if (sameString(command, "position")) { char *chrom = NULL; int start = 0, end = 0; if (wordCount < 3) { err = "Expecting 3 words in browser position line"; break; } if (!hgParseChromRange(database, words[2], &chrom, &start, &end) || start < 0 || end > hChromSize(database, chrom)) { err ="Invalid browser position (use chrN:123-456 format)"; break; } cartSetString(cart, "position", words[2]); } } } if (retErr) *retErr = err; } #ifdef PROGRESS_METER static void progressMeter() { printf("<FORM STYLE=\"margin-bottom:0;\" ACTION=\"%s\" METHOD=\"GET\" NAME=\"orgForm\">", hgCustomName()); cartSaveSession(cart); printf("<INPUT TYPE=\"HIDDEN\" NAME=\"org\" VALUE=\"%s\">\n", organism); printf("<INPUT TYPE=\"HIDDEN\" NAME=\"db\" VALUE=\"%s\">\n", database); printf("<INPUT TYPE=\"HIDDEN\" NAME=\"hgct_do_add\" VALUE=\"1\">\n"); puts("</FORM>"); } static void doProgress(char *err) /* display progress meter to show loading process */ { cartWebStart(cart, database, "Custom Track loading progress meter"); progressMeter(); // addCustomForm(NULL, err); helpCustom(); cartWebEnd(cart); } #endif static void webBotWarning() /* display the overuse warning message in the javaScript warning text box * html output has already started at this point, just need to add the * warning handler and setup the warning javaScript box, warnings after * this will go to that text box */ { pushWarnHandler(webVaWarn); htmlWarnBoxSetup(stdout); char *ip = getenv("REMOTE_ADDR"); botDelayMessage(ip, botDelayMillis); } void doAddCustom(char *err, boolean warnOnly) /* display form for adding custom tracks. * Include error message, if any */ { cartWebStart(cart, database, "Add Custom Tracks"); addCustomForm(NULL, err, warnOnly); helpCustom(); if (issueBotWarning) webBotWarning(); cartWebEnd(cart); } void doUpdateCustom(struct customTrack *ct, char *err, boolean warnOnly) /* display form for adding custom tracks. * Include error message, if any */ { char *longLabel = htmlEncode(ct->tdb->longLabel); cartWebStart(cart, database, "Update Custom Track: %s [%s]", longLabel, database); freeMem(longLabel); cartSetString(cart, hgCtDocText, ct->tdb->html); addCustomForm(ct, err, warnOnly); helpCustom(); cartWebEnd(cart); } static void doManageCustom(char *warnMsg) /* display form for deleting & updating custom tracks. * Include warning message, if any */ { cartWebStart(cart, database, "Manage Custom Tracks"); jsIncludeFile("jquery.js", NULL); manageCustomForm(warnMsg); webNewSection("Managing Custom Tracks"); webIncludeHelpFile("customTrackManage", FALSE); if (issueBotWarning) webBotWarning(); cartWebEnd(cart); } char *fixNewData(struct cart *cart) /* append a newline to incoming data, to keep custom preprocessor happy */ { char *customText = cartUsualString(cart, hgCtDataText, ""); if (isNotEmpty(customText)) { struct dyString *ds = dyStringNew(0); dyStringPrintf(ds, "%s\n", customText); customText = dyStringCannibalize(&ds); cartSetString(cart, hgCtDataText, customText); } return customText; } char *replacedTracksMsg(struct customTrack *replacedCts) /* make warning message listing replaced tracks */ { struct customTrack *ct; if (!slCount(replacedCts)) return NULL; struct dyString *dsWarn = dyStringNew(0); dyStringAppend(dsWarn, "Replaced: "); for (ct = replacedCts; ct != NULL; ct = ct->next) { if (ct != replacedCts) /* not the first */ dyStringAppend(dsWarn, ", "); dyStringAppend(dsWarn, ct->tdb->shortLabel); } return dyStringCannibalize(&dsWarn); } void doDeleteCustom() /* remove custom tracks from list based on cart variables */ { struct customTrack *ct; for (ct = ctList; ct != NULL; ct = ct->next) { char var[256]; safef(var, sizeof var, "%s_%s", hgCtDeletePrefix, ct->tdb->track); if (cartUsualBoolean(cart, var, FALSE)) slRemoveEl(&ctList, ct); } } void doRefreshCustom(char **warnMsg) /* reparse custom tracks from URLs based on cart variables */ { struct customTrack *ct; struct customTrack *replacedCts = NULL; struct customTrack *refreshCts = NULL; for (ct = ctList; ct != NULL; ct = ct->next) { char var[256]; safef(var, sizeof var, "%s_%s", hgCtRefreshPrefix, ct->tdb->track); if (cartUsualBoolean(cart, var, FALSE)) { struct customTrack *nextCt = NULL, *urlCt = NULL; struct customTrack *urlCts = customFactoryParse(database, ctDataUrl(ct), FALSE, NULL); for (urlCt = urlCts; urlCt != NULL; urlCt = nextCt) { nextCt = urlCt->next; if (sameString(ct->tdb->track, urlCt->tdb->track)) slAddHead(&refreshCts, urlCt); } } } ctList = customTrackAddToList(ctList, refreshCts, &replacedCts, FALSE); if (warnMsg) *warnMsg = replacedTracksMsg(replacedCts); customTrackHandleLift(database, ctList); } void addWarning(struct dyString *ds, char *msg) /* build up a warning message from parts */ { if (!msg) return; if (isNotEmpty(ds->string)) dyStringAppend(ds, ". "); dyStringAppend(ds, msg); } char *saveLines(char *text, int max) /* save lines from input, up to 'max'. * Prepend with comment, if truncated */ { if (!text) return NULL; char buf[128]; int count = 0; char *line; boolean truncated = FALSE; struct dyString *ds = dyStringNew(0); safef(buf, sizeof buf, "# Displaying first %d lines of data", max); struct lineFile *lf = lineFileOnString("saved custom text", TRUE, text); while (lineFileNext(lf, &line, NULL)) { if (startsWith(buf, line)) continue; if (++count > max) { truncated = TRUE; break; } dyStringAppend(ds, line); dyStringAppend(ds, "\n"); } if (truncated) { struct dyString *dsNew = dyStringNew(0); dyStringPrintf(dsNew, "%s\n%s", buf, dyStringCannibalize(&ds)); return dyStringCannibalize(&dsNew); } return (dyStringCannibalize(&ds)); } struct customTrack *ctFromList(struct customTrack *ctList, char *track) /* return custom track from list */ { 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, *warnMsg = NULL; char *selectedTable = NULL; struct customTrack *ct = NULL; boolean ctUpdated = FALSE; char *initialDb = NULL; long thisTime = clock1000(); cart = theCart; measureTiming = isNotEmpty(cartOptionalString(cart, "measureTiming")); initialDb = cloneString(cartUsualString(cart, "db", "")); getDbAndGenome(cart, &database, &organism, oldVars); chromAliasSetup(database); customFactoryEnableExtraChecking(TRUE); knetUdcInstall(); if (udcCacheTimeout() < 300) udcSetCacheTimeout(300); if (sameString(initialDb, "0")) { /* when an organism is selected from the custom track management page, * set the database to be the default only if it has custom tracks. * Otherwise, pick an assembly for that organism that does have custom tracks. */ struct dbDb *dbDb, *dbDbs = getCustomTrackDatabases(); char *dbWithCts = NULL; for (dbDb = dbDbs; dbDb != NULL; dbDb = dbDb->next) { if (sameString(database, dbDb->name)) break; if (sameString(organism, dbDb->organism)) { if (!dbWithCts) dbWithCts = cloneString(dbDb->name); } } if (dbWithCts) // set the database for the selected organism to an assembly that { // has custom tracks database = dbWithCts; cartSetString(cart, "db", database); } } if (cartVarExists(cart, hgCtDoAdd)) doAddCustom(NULL, FALSE); #ifdef PROGRESS_METER else if (cartVarExists(cart, hgCtDoProgress)) { doProgress(NULL); } #endif else if (cartVarExists(cart, hgCtTable)) { /* update track */ /* need to clone the hgCtTable value, as the ParseCart will remove 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, FALSE); else doAddCustom(NULL, FALSE); } 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); boolean warnOnly = FALSE; 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); if (!trackConfig) trackConfig = cartOptionalString(cart, hgCtUpdatedTrack); char *fileContents = NULL; if (isNotEmpty(fileName)) { if (customTrackIsCompressed(fileName)) fileContents = "Compressed files not supported for data update"; else fileContents = cartOptionalString(cart, hgCtDataFile); customText = fileContents; } /* check for duplicate track config in config and data entry */ if (customTrackHasConfig(trackConfig) && customTrackHasConfig(customText)) { if (startsWith(trackConfig, customText)) trackConfig = ""; else customText = "Duplicate track configuration found - remove track and browser lines from Configuration box or from Data"; } dyStringPrintf(dsTrack, "%s\n%s\n", trackConfig, customText); customText = dyStringCannibalize(&dsTrack); cartSetString(cart, hgCtDataText, customText); if (isNotEmpty(fileContents)) { /* already handled file */ cartRemove(cart, hgCtDataFile); cartRemove(cart, hgCtDataFileName); } } warnOnly = FALSE; struct errCatch *catch = errCatchNew(); if (errCatchStart(catch)) ctList = customTracksParseCartDetailed(database, cart, &browserLines, &ctFileName, &replacedCts, NULL, &err, &warnOnly); errCatchEnd(catch); if (catch->gotError) { addWarning(dsWarn, err); } if (isNotEmpty(catch->message->string)) { addWarning(dsWarn, catch->message->string); } errCatchFree(&catch); /* exclude special setting used by table browser to indicate * db assembly for error-handling purposes only */ char *db = NULL; if (trackConfig && (db = stringIn("db=", trackConfig)) != NULL) { db += 3; char *nextTok = skipToSpaces(db); if (!nextTok) nextTok = strchr(db, 0); db = cloneStringZ(db,nextTok-db); stripChar(db, '\''); stripChar(db, '"'); if (!sameString(db,database)) err = "Invalid configuration found - remove db= or return it to its original value. "; } if (cartVarExists(cart, hgCtUpdatedTrack) && !hasData) { /* update custom track config and doc, but not data*/ selectedTable = cartOptionalString(cart, hgCtUpdatedTable); if (selectedTable) { ct = ctFromList(ctList, selectedTable); if (ct) { struct errCatch *catch = errCatchNew(); if (errCatchStart(catch)) { customTrackUpdateFromConfig(ct, database, trackConfig, &browserLines); ctUpdated = TRUE; } errCatchEnd(catch); if (isNotEmpty(catch->message->string)) err = catTwoStrings(emptyForNull(err), catch->message->string); errCatchFree(&catch); } } } addWarning(dsWarn, replacedTracksMsg(replacedCts)); doBrowserLines(browserLines, &warnMsg); addWarning(dsWarn, warnMsg); if (err && !(warnOnly && cartVarExists(cart, "ContinueWithWarn"))) { char *selectedTable = NULL; cartSetString(cart, hgCtDataText, savedCustomText); cartSetString(cart, hgCtConfigLines, savedConfig); if ((selectedTable= cartOptionalString(cart, hgCtUpdatedTable)) != NULL) { ct = ctFromList(ctList, selectedTable); doUpdateCustom(ct, err, warnOnly); } else doAddCustom(err, warnOnly); cartRemovePrefix(cart, hgCt); return; } if (cartVarExists(cart, hgCtDoDelete)) { doDeleteCustom(); ctUpdated = TRUE; } if (cartVarExists(cart, hgCtDoRefresh)) { doRefreshCustom(&warnMsg); addWarning(dsWarn, warnMsg); ctUpdated = TRUE; } if (ctUpdated || ctConfigUpdate(ctFileName)) { customTracksSaveCart(database, cart, ctList); /* refresh ctList again to pickup remote resource error state */ warnOnly=FALSE; struct errCatch *catch = errCatchNew(); if (errCatchStart(catch)) ctList = customTracksParseCartDetailed(database, cart, &browserLines, &ctFileName, &replacedCts, NULL, &err, &warnOnly); errCatchEnd(catch); if (catch->gotError) { addWarning(dsWarn, err); addWarning(dsWarn, catch->message->string); } errCatchFree(&catch); } warnMsg = dyStringCannibalize(&dsWarn); if (measureTiming) { long lastTime = clock1000(); loadTime = lastTime - thisTime; } if (ctList || cartVarExists(cart, hgCtDoDelete)) doManageCustom(warnMsg); else doAddCustom(warnMsg, warnOnly); } cartRemovePrefix(cart, hgCt); cartRemove(cart, CT_CUSTOM_TEXT_VAR); } int main(int argc, char *argv[]) /* Process command line. */ { long enteredMainTime = clock1000(); issueBotWarning = earlyBotCheck(enteredMainTime, "hgCustom", delayFraction, 0, 0, "html"); htmlPushEarlyHandlers(); oldVars = hashNew(10); cgiSpoof(&argc, argv); cartEmptyShell(doMiddle, hUserCookie(), excludeVars, oldVars); cgiExitTime("hgCustom", enteredMainTime); return 0; }