05094dabf3d005ab35a998ff225443fc4255611f galt Mon Feb 4 23:07:42 2019 -0800 Adding ability to backup and restore the cart and its custom tracks in hgSession. diff --git src/hg/hgSession/hgSession.c src/hg/hgSession/hgSession.c index 864a092..c15aecc 100644 --- src/hg/hgSession/hgSession.c +++ src/hg/hgSession/hgSession.c @@ -19,34 +19,38 @@ #include "linefile.h" #include "net.h" #include "textOut.h" #include "hCommon.h" #include "hui.h" #include "cart.h" #include "jsHelper.h" #include "web.h" #include "hdb.h" #include "ra.h" #include "wikiLink.h" #include "customTrack.h" #include "customFactory.h" #include "udc.h" #include "hgSession.h" -#include "hubConnect.h" #include "hgConfig.h" #include "sessionThumbnail.h" +#include "filePath.h" #include "obscure.h" +#include "trashDir.h" +#include "hubConnect.h" + +#include "trackHub.h" void usage() /* Explain usage and exit. */ { errAbort( "hgSession - Interface with wiki login and do session saving/loading.\n" "usage:\n" " hgSession <various CGI settings>\n" ); } /* Global variables. */ struct cart *cart; char *excludeVars[] = {"Submit", "submit", NULL}; struct slName *existingSessionNames = NULL; @@ -295,31 +299,32 @@ " \"order\":[1,'asc'],\n" " \"stateSave\":true,\n" " \"stateSaveCallback\": %s,\n" " \"stateLoadCallback\": %s\n" " });\n" "} );\n" "}\n" , jsDataTableStateSave(hgSessionPrefix), jsDataTableStateLoad(hgSessionPrefix, cart)); printf("<H3>My Sessions</H3>\n"); printf("<div style=\"max-width:1024px\">"); printf("<table id=\"sessionTable\" class=\"sessionTable stripe hover row-border compact\" borderwidth=0>\n"); printf("<thead><tr>"); printf("<TH><TD><B>session name (click to load)</B></TD><TD><B>created on</B></TD><td><b>assembly</b></td>" "<TD align=center><B>view/edit <BR>details </B></TD>" - "<TD align=center><B>delete this <BR>session </B></TD><TD align=center><B>share with <BR>others? </B></TD>" + "<TD align=center><B>delete this <BR>session </B></TD>" + "<TD align=center><B>share with <BR>others? </B></TD>" "<td align-center><b>post in <br><a href=\"../cgi-bin/hgPublicSessions?%s\">public listing</a>?</b></td>" "<TD align=center><B>send to<BR>mail</B></TD></TH>", cartSidUrlString(cart)); printf("</tr></thead>"); printf("<tbody>\n"); if (gotSettings) sqlSafef(query, sizeof(query), "SELECT sessionName, shared, firstUse, contents, settings from %s " "WHERE userName = '%s' ORDER BY sessionName;", namedSessionTable, encUserName); else sqlSafef(query, sizeof(query), "SELECT sessionName, shared, firstUse, contents from %s " "WHERE userName = '%s' ORDER BY sessionName;", namedSessionTable, encUserName); sr = sqlGetResult(conn, query); @@ -363,30 +368,31 @@ db = cloneStringZ(dbIdx, dbEnd-dbIdx); else db = cloneString(dbIdx); printf("%s</td><td align=center>", db); } else printf("n/a</td><td align=center>"); if (gotSettings) { safef(buf, sizeof(buf), "%s%s", hgsEditPrefix, encSessionName); cgiMakeButton(buf, "details"); } else printf("unavailable"); + printf("</TD><TD align=center>"); safef(buf, sizeof(buf), "%s%s", hgsDeletePrefix, encSessionName); char command[512]; safef(command, sizeof(command), confirmDeleteFormat, encSessionName); cgiMakeOnClickSubmitButton(command, buf, "delete"); printf("</TD><TD align=center>"); safef(buf, sizeof(buf), "%s%s", hgsSharePrefix, encSessionName); cgiMakeCheckBoxWithId(buf, shared>0, buf); jsOnEventById("change",buf,"console.log('new status' + this.checked); document.mainForm.submit();"); printf("</TD><TD align=center>"); safef(buf, sizeof(buf), "%s%s", hgsGalleryPrefix, encSessionName); cgiMakeCheckBoxFourWay(buf, inGallery, shared>0, buf, NULL, NULL); jsOnEventById("change", buf, "document.mainForm.submit();"); @@ -446,30 +452,55 @@ jsOnEventById("keypress", hgsLoadLocalFileName,"return noSubmitOnEnter(event);"); printf(" "); cgiMakeButton(hgsDoLoadLocal, "submit"); printf("</TD></TR>\n"); printf("<TR><TD colspan=2></TD></TR>\n"); printf("<TR><TD colspan=2>Use settings from a URL (http://..., ftp://...):" "</TD>\n"); printf("<TD>\n"); cgiMakeOnKeypressTextVar(hgsLoadUrlName, cartUsualString(cart, hgsLoadUrlName, ""), 20, jsPressOnEnter(hgsDoLoadUrl)); printf(" "); cgiMakeButton(hgsDoLoadUrl, "submit"); printf("</TD></TR>\n"); + +printf("<TR><TD colspan=2></TD></TR>\n"); + +char name[256]; +safef(name, sizeof name, "%s", hgsExtractUploadPrefix); + +printf("<TR>"); +printf("<TD colspan=2>Use settings including custom tracks in local backup archive file (.tar.gz):</TD>"); +printf("<TD><INPUT TYPE=FILE NAME='%s' id='%s'>", name, name); +printf(" <input type='submit' id='archiveUpload' value='submit'></TD></TR>"); +printf("<TR><TD colspan=2></TD></TR>"); + +// check the file name, e.g. does it have the right name or extension? +char js[1024]; +safef(js, sizeof js, +"var control = document.getElementById('%s');" +"var files = control.files;" +"if ((files.length == 1) && (files[0].name.endsWith('.tar.gz')))" +" return true;" +"alert('Please choose the correct backup archive ending in .tar.gz');" +"return false;" +, name); + +jsOnEventById("click", "archiveUpload", js); + printf("</TABLE>\n"); printf("<P></P>\n"); } void showSavingOptions(char *userName) /* Show options for saving a new named session in our db or to a file. */ { printf("<H3>Save Settings</H3>\n"); printf("<TABLE BORDERWIDTH=0>\n"); if (isNotEmpty(userName)) { printf("<TR><TD colspan=4>Save current settings as named session:" "</TD></TR>\n" "<TR><TD> </TD><TD>name:</TD><TD>\n"); @@ -519,30 +550,40 @@ printf("file type returned: "); char *compressType = cartUsualString(cart, hgsSaveLocalFileCompress, textOutCompressNone); cgiMakeRadioButton(hgsSaveLocalFileCompress, textOutCompressNone, differentWord(textOutCompressGzip, compressType)); printf(" plain text  "); cgiMakeRadioButton(hgsSaveLocalFileCompress, textOutCompressGzip, sameWord(textOutCompressGzip, compressType)); printf(" gzip compressed (ignored if output file is blank)"); printf("</TD><TD>"); printf(" "); cgiMakeButton(hgsDoSaveLocal, "submit"); printf("</TD></TR>\n"); printf("<TR><TD></TD><TD colspan=3>(leave file blank to get output in " "browser window)</TD></TR>\n"); printf("<TR><TD colspan=4></TD></TR>\n"); + +printf("<TR><TD colspan=4>Save Full Session:</TD></TR>\n"); +printf("<TR><TD> </TD><TD colspan=2>"); +printf("backup settings including custom tracks to archive .tar.gz</TD>"); +printf("<TD>"); +printf(" "); +cgiMakeButton(hgsShowDownloadPrefix, "submit"); +printf("</TD></TR>\n"); + +printf("<TR><TD colspan=4></TD></TR>\n"); printf("</TABLE>\n"); } void showSessionControls(char *userName, boolean savedSessionsSupported, boolean webStarted) /* If userName is non-null, show sessions that belong to user and allow * saving of named sessions. * If savedSessionsSupported, allow import of named sessions. * Allow export/import of settings from file/URL. */ { char *formMethod = cartUsualString(cart, "formMethod", "POST"); if (webStarted) webNewSection("Session Management"); else @@ -675,30 +716,35 @@ void cleanHgSessionFromCart(struct cart *cart) /* Remove hgSession action variables that should not stay in the cart. */ { char varName[256]; safef(varName, sizeof(varName), "%s%s", cgiBooleanShadowPrefix(), hgsSharePrefix); cartRemovePrefix(cart, varName); cartRemovePrefix(cart, hgsSharePrefix); safef(varName, sizeof(varName), "%s%s", cgiBooleanShadowPrefix(), hgsGalleryPrefix); cartRemovePrefix(cart, varName); cartRemovePrefix(cart, hgsGalleryPrefix); cartRemovePrefix(cart, hgsLoadPrefix); cartRemovePrefix(cart, hgsEditPrefix); cartRemovePrefix(cart, hgsLoadLocalFileName); cartRemovePrefix(cart, hgsDeletePrefix); +cartRemovePrefix(cart, hgsShowDownloadPrefix); +cartRemovePrefix(cart, hgsMakeDownloadPrefix); +cartRemovePrefix(cart, hgsDoDownloadPrefix); +cartRemovePrefix(cart, hgsExtractUploadPrefix); +cartRemovePrefix(cart, hgsDoUploadPrefix); cartRemovePrefix(cart, hgsDo); cartRemove(cart, hgsOldSessionName); cartRemove(cart, hgsCancel); } static void outIfNotPresent(struct cart *cart, struct dyString *dy, char *track, int tdbVis) /* Output default trackDb visibility if it's not mentioned in the cart. */ { char *cartVis = cartOptionalString(cart, track); if (cartVis == NULL) { if (dy) dyStringPrintf(dy,"&%s=%s", track, hStringFromTv(tdbVis)); else printf("%s %s\n", track, hStringFromTv(tdbVis)); @@ -965,31 +1011,33 @@ "<INPUT TYPE=TEXT NAME=\"%s\" id='%s' SIZE=%d VALUE=\"%s\" >\n", sessionName, hgSessionName(), cartSessionVarName(cart), cartSessionId(cart), hgsOldSessionName, sessionName, hgsNewSessionName, hgsNewSessionName, 32, sessionName); jsOnEventById("change" , hgsNewSessionName, highlightAccChanges); jsOnEventById("keypress", hgsNewSessionName, highlightAccChanges); dyStringPrintf(dyMessage, " <INPUT TYPE=SUBMIT NAME=\"%s%s\" VALUE=\"use\">" " <INPUT TYPE=SUBMIT NAME=\"%s%s\" id='%s%s' VALUE=\"delete\">" " <INPUT TYPE=SUBMIT ID=\"%s\" NAME=\"%s\" VALUE=\"accept changes\">" " <INPUT TYPE=SUBMIT NAME=\"%s\" VALUE=\"cancel\"> " "<BR>\n", hgsLoadPrefix, encSessionName, hgsDeletePrefix, encSessionName, hgsDeletePrefix, encSessionName, - hgsDoSessionChange, hgsDoSessionChange, hgsCancel); + hgsDoSessionChange, hgsDoSessionChange, + hgsCancel); + char id[256]; safef(id, sizeof id, "%s%s", hgsDeletePrefix, encSessionName); jsOnEventByIdF("click", id, confirmDeleteFormat, encSessionName); dyStringPrintf(dyMessage, "Share with others? <INPUT TYPE=CHECKBOX NAME=\"%s%s\"%s VALUE=on " "id=\"detailsSharedCheckbox\">\n" "<INPUT TYPE=HIDDEN NAME=\"%s%s%s\" VALUE=0><BR>\n", hgsSharePrefix, encSessionName, (shared>0 ? " CHECKED" : ""), cgiBooleanShadowPrefix(), hgsSharePrefix, encSessionName); jsOnEventByIdF("change", "detailsSharedCheckbox", "{%s %s}", highlightAccChanges, toggleGalleryDisable); jsOnEventByIdF("click" , "detailsSharedCheckbox", "{%s %s}", highlightAccChanges, toggleGalleryDisable); dyStringPrintf(dyMessage, "List in Public Sessions? <INPUT TYPE=CHECKBOX NAME=\"%s%s\"%s VALUE=on " @@ -1412,30 +1460,35 @@ char *newName = cartOptionalString(cart, hgsNewSessionName); if (isNotEmpty(newName) && !sameString(sessionName, newName)) { char *encNewName = cgiEncodeFull(newName); sqlSafef(query, sizeof(query), "UPDATE %s set sessionName = '%s' WHERE userName = '%s' AND sessionName = '%s';", namedSessionTable, encNewName, encUserName, encSessionName); sqlUpdate(conn, query); dyStringPrintf(dyMessage, "Changed session name from %s to <B>%s</B>.\n", sessionName, newName); sessionName = newName; encSessionName = encNewName; renamePrefixedCartVar(hgsEditPrefix , encOldSessionName, encNewName); renamePrefixedCartVar(hgsLoadPrefix , encOldSessionName, encNewName); renamePrefixedCartVar(hgsDeletePrefix , encOldSessionName, encNewName); + renamePrefixedCartVar(hgsShowDownloadPrefix , encOldSessionName, encNewName); + renamePrefixedCartVar(hgsMakeDownloadPrefix , encOldSessionName, encNewName); + renamePrefixedCartVar(hgsDoDownloadPrefix , encOldSessionName, encNewName); + renamePrefixedCartVar(hgsExtractUploadPrefix, encOldSessionName, encNewName); + renamePrefixedCartVar(hgsDoUploadPrefix , encOldSessionName, encNewName); if (shared >= 2) { thumbnailRemove(encUserName, encSessionName, conn); thumbnailAdd(encUserName, encNewName, conn, dyMessage); } } char sharedVarName[256]; char galleryVarName[256]; safef(sharedVarName, sizeof(sharedVarName), hgsSharePrefix "%s", encOldSessionName); safef(galleryVarName, sizeof(galleryVarName), hgsGalleryPrefix "%s", encOldSessionName); if (cgiBooleanDefined(sharedVarName) || cgiBooleanDefined(galleryVarName)) { int newShared = shared; if (cgiBooleanDefined(sharedVarName)) @@ -1499,45 +1552,262 @@ namedSessionTable, dyRa->string, encUserName, encSessionName); sqlUpdate(conn, dyQuery->string); dyStringPrintf(dyMessage, "Updated description of <B>%s</B>.\n", sessionName); } } if (isEmpty(dyMessage->string)) dyStringPrintf(dyMessage, "No changes to session <B>%s</B>.\n", sessionName); dyStringPrintf(dyMessage, "%s %s", getSessionLink(encUserName, encSessionName), getSessionEmailLink(encUserName, encSessionName)); if (shared) printShareMessage(dyMessage, encUserName, encSessionName, FALSE); return dyStringCannibalize(&dyMessage); } +// ====================================== + +void prepBackGroundCall(char **pBackgroundProgress, char *cleanPrefix) +/* fix cart and save state */ +{ +*pBackgroundProgress = cloneString(cgiUsualString("backgroundProgress", NULL)); +cartRemove(cart, "backgroundExec"); +cartRemove(cart, "backgroundProgress"); +cartRemovePrefix(cart, cleanPrefix); +cartSaveState(cart); // in case it crashes +} + +void launchForeAndBackGround(char *operation) +/* update cart, launch background and foreground */ +{ +char cmd[1024]; +safef(cmd, sizeof cmd, "./hgSession backgroundExec=%s", operation); + +// allow child to see variables loaded from CGI. +// because CGI settings have not been saved back to the cart yet +cartSaveState(cart); + +char *workUrl = NULL; +// automatically adds hgsid +// automatically adds backGroundProgress=%s url.progress for separate channel +// Have to pass the userName manually since background exec will not get cookie, +// but we are no longer using userName which was needed with saved-sessions. +startBackgroundWork(cmd, &workUrl); + +htmlOpen("Background Status"); + +jsInlineF( + "setTimeout(function(){location = 'hgSession?backgroundStatus=%s&hgsid=%s';},2000);\n", + cgiEncode(workUrl), cartSessionId(cart)); +htmlClose(); +fflush(stdout); +} + +void passSubmittedBinaryAsTrashFile(struct hashEl *list) +/* fetch the binary file submitted in memory, + * and save it to temp trash location, + * saving the name in the cart. + * This is necessary to pass the file to the background process.*/ +{ +// List should have these two +// hgS_extractUpload_hub_9614_Anc11__binary +// hgS_extractUpload_hub_9614_Anc11__filename +// can have a third __filepath if crashes leaving in the cart. + +char *binaryParam = NULL; +struct hashEl *hel = NULL; +for (hel = list; hel; hel = hel->next) + { + if (endsWith(hel->name, "__binary")) + binaryParam = cloneString(hel->name); + } + +if (!binaryParam) + { + htmlOpen("No file selected"); + printf("Please choose a saved session custom tracks local backup archive file (.tar.gz) to upload"); + htmlClose(); + exit(0); + } + +char *binaryValue = cartOptionalString(cart, binaryParam); + +char *binInfo = cloneString(binaryValue); +char *words[2]; +char *mem; +unsigned long size; +chopByWhite(binInfo, words, ArraySize(words)); +mem = (char *)sqlUnsignedLong(words[0]); +size = sqlUnsignedLong(words[1]); + +struct tempName tn; +trashDirFile(&tn, "backGround", cartSessionId(cart), ".bin"); + +writeGulp(tn.forCgi, mem, size); + +// add new cart var with trash path +// hgS_extractUpload_hub_9614_Anc11__filepath +char *varName = replaceChars(binaryParam, "__binary", "__filepath"); +cartRemove(cart, varName); // just in case +cartSetString(cart, varName, tn.forCgi); // update the cart +} void hgSession() /* hgSession - Interface with wiki login and do session saving/loading. * Here we set up cart and some global variables, dispatch the command, * and put away the cart when it is done. */ { struct hash *oldVars = hashNew(10); /* Sometimes we output HTML and sometimes plain text; let each outputter * take care of headers instead of using a fixed cart*Shell(). */ cart = cartAndCookieNoContent(hUserCookie(), excludeVars, oldVars); char *userName = (loginSystemEnabled() || wikiLinkEnabled()) ? wikiLinkUserName() : NULL; -if (cartVarExists(cart, hgsDoMainPage) || cartVarExists(cart, hgsCancel)) +char *backgroundStatus = cloneString(cartUsualString(cart, "backgroundStatus", NULL)); + +if (backgroundStatus) + { + // clear backgroundStatus from the cart + cartRemove(cart, "backgroundStatus"); + /* Save cart variables. */ + cartSaveState(cart); + getBackgroundStatus(backgroundStatus); + exit(0); + } + +char *backgroundExec = cloneString(cgiUsualString("backgroundExec", NULL)); + + +struct hashEl *showDownloadList = cartFindPrefix(cart, hgsShowDownloadPrefix); +struct hashEl *makeDownloadList = cartFindPrefix(cart, hgsMakeDownloadPrefix); +struct hashEl *doDownloadList = cartFindPrefix(cart, hgsDoDownloadPrefix); +struct hashEl *extractUploadList = cartFindPrefix(cart, hgsExtractUploadPrefix); +struct hashEl *doUploadList = cartFindPrefix(cart, hgsDoUploadPrefix); + +// The form gets submitted but no filename for upload was chosen and submitted. +if (extractUploadList && sameString(hgsExtractUploadPrefix, extractUploadList->name)) + extractUploadList = NULL; // no filename was submitted. ignore. + +if (showDownloadList) + showDownloadSessionCtData(showDownloadList); +else if (makeDownloadList) + { + if (sameOk(backgroundExec,"makeDownloadSessionCtData")) + { + // only one, not a list. + struct hashEl *hel = makeDownloadList; + char *param1 = cloneString(hel->name); + + char *backgroundProgress = NULL; + prepBackGroundCall(&backgroundProgress, hgsMakeDownloadPrefix); + + makeDownloadSessionCtData(param1, backgroundProgress); + + exit(0); // cannot return + } + else + { + + launchForeAndBackGround("makeDownloadSessionCtData"); + + exit(0); + } + + } +else if (doDownloadList) + doDownloadSessionCtData(doDownloadList); +else if (extractUploadList) + { + if (sameOk(backgroundExec,"extractUploadSessionCtData")) + { + // only one, but becomes a list + // since there are multiple cart names with suffixes for handling binary submitted file. + //hgS_extractUpload___filepath + //hgS_extractUpload___binary + //hgS_extractUpload___filename + char *param1 = NULL; + char *param2 = NULL; + char *param3 = NULL; + struct hashEl *hel = NULL; + for (hel = extractUploadList; hel; hel = hel->next) + { + if (endsWith(hel->name, "__binary")) + param1 = cloneString(hel->name); + if (endsWith(hel->name, "__filename")) + param2 = cloneString(hel->name); + if (endsWith(hel->name, "__filepath")) + param3 = cloneString(hel->name); + } + if (!param1) + errAbort("missing __binary param"); + if (!param2) + errAbort("missing __filename param"); + if (!param3) + errAbort("missing __filepath param"); + + char *param1Value = cloneString(cartOptionalString(cart, param1)); + char *param2Value = cloneString(cartOptionalString(cart, param2)); + char *param3Value = cloneString(cartOptionalString(cart, param3)); + + char *backgroundProgress = NULL; + prepBackGroundCall(&backgroundProgress, hgsExtractUploadPrefix); + + extractUploadSessionCtData( + param1, param1Value, + param2, param2Value, + param3, param3Value, + backgroundProgress); + + exit(0); // cannot return + } + else + { + passSubmittedBinaryAsTrashFile(extractUploadList); + + launchForeAndBackGround("extractUploadSessionCtData"); + + exit(0); + } + + } +else if (doUploadList) + { + if (sameOk(backgroundExec,"doUploadSessionCtData")) + { + // only one, not a list. + struct hashEl *hel = doUploadList; + char *param1 = cloneString(hel->name); + + char *backgroundProgress = NULL; + prepBackGroundCall(&backgroundProgress, hgsDoUploadPrefix); + + doUploadSessionCtData(param1, backgroundProgress); + + exit(0); // cannot return + } + else + { + + launchForeAndBackGround("doUploadSessionCtData"); + + exit(0); + } + + } +else if (cartVarExists(cart, hgsDoMainPage) || cartVarExists(cart, hgsCancel)) doMainPage(userName, NULL); else if (cartVarExists(cart, hgsDoNewSession)) { char *message = doNewSession(userName); doMainPage(userName, message); } else if (cartVarExists(cart, hgsDoOtherUser)) { char *message = doOtherUser(hgsDoOtherUser); doMainPage(userName, message); } else if (cartVarExists(cart, hgsDoSaveLocal)) { doSaveLocal(); }