770ed6ada89bdaaac49ba96963510af0a9338b89 galt Thu Feb 14 11:44:29 2019 -0800 hgSession custom track backup archive creation for downloading. Changing approach from backupAndRestore to backupOnly. Uses just plain real ascii .ct custom tracks. diff --git src/hg/hgSession/backup.c src/hg/hgSession/backup.c new file mode 100644 index 0000000..f7b58a6 --- /dev/null +++ src/hg/hgSession/backup.c @@ -0,0 +1,1163 @@ +/* backupRestore - Routines related to backing up and restoring the current cart and its custom tracks. */ + +/* Copyright (C) 2014 The Regents of the University of California + * See README in this or parent directory for licensing information. */ + + +#include "common.h" + +#include "cart.h" +#include "net.h" +#include "textOut.h" +#include "base64.h" +#include "md5.h" +#include "obscure.h" +#include "net.h" +#include "hgConfig.h" + +#include "htmshell.h" +#include "cheapcgi.h" +#include "hgSession.h" + +#include + +#include "hCommon.h" +#include "hdb.h" +#include "trackHub.h" +#include "hubConnect.h" +#include "customFactory.h" +#include "jsHelper.h" +#include "trashDir.h" +#include "filePath.h" +#include "cgiApoptosis.h" + +#include "wiggle.h" + + +#define UCSC_GB_BACKUP_VERSION_FILENAME "UCSC_GB_BACKUP_VERSION" + + +static void errAbortHandler(char *format, va_list args) +{ +// provide more explicit message when we run out of memory (#5147). +if(strstr(format, "needLargeMem:") || strstr(format, "carefulAlloc:")) + htmlVaWarn("Region selected is too large for calculation. Please specify a smaller region or try limiting to fewer data points.", args); +else + { + // call previous handler + popWarnHandler(); + vaWarn(format, args); + } +if(isErrAbortInProgress()) + noWarnAbort(); +} + +static void vaHtmlOpen(char *format, va_list args) +/* Start up a page that will be in html format. */ +{ +puts("Content-Type:text/html\n"); +cartVaWebStart(cart, database, format, args); +pushWarnHandler(errAbortHandler); +} + +void htmlOpen(char *format, ...) +/* Start up a page that will be in html format. */ +{ +va_list args; +va_start(args, format); +vaHtmlOpen(format, args); +va_end(args); +} + +void htmlClose() +/* Close down html format page. */ +{ +popWarnHandler(); +cartWebEnd(); +} + +// ------------------------------------------ + + +void getBackgroundStatus(char *url) +/* fetch status as the latest complete html block available. + * fetch progress info instead if background proc still running. */ +{ +char *html = NULL; +if (fileSize(url)==0) + { + htmlOpen("Background Status"); + errAbort("No output found. Expecting output in [%s].", url); + htmlClose(); + return; + } + +// get the pid and see if it is still running +char pidName[1024]; +safef(pidName, sizeof pidName, "%s.pid", url); +FILE *pidF = mustOpen(pidName, "r"); +long pid = 0; +fscanf(pidF, "%ld", &pid); +carefulClose(&pidF); + +// If the background process is still running, grab the progress file instead +if (getpgid(pid) >= 0) + { + htmlOpen("Background Status"); + char progressName[1024]; + safef(progressName, sizeof progressName, "%s.progress", url); + if (fileExists(progressName)) + { + readInGulp(progressName, &html, NULL); + printf("%s",html); + } + else + { + printf("progress file missing
\n"); + } + + printf("
\n
\n" + //"" + "" + "
\n" + ); + jsOnEventById("click", "Refresh", "window.location=window.location;return false;"); + jsInline("setTimeout(function(){location = location;},5000);\n"); + jsInline("window.scrollTo(0,document.body.scrollHeight);"); + + htmlClose(); + return; + } + +// otherwise read the main html file + +readInGulp(url, &html, NULL); +int numLines = chopString(html, "\n", NULL, 1000000); +char **lines = NULL; +AllocArray(lines, numLines); +chopString(html, "\n", lines, numLines); +int end; +for (end=numLines-1; end >= 0 && ! (endsWith(lines[end], "") || endsWith(lines[end], "")) ; --end) + /* do nothing */ ; +if (end < 0) + { + htmlOpen("Background Status"); + errAbort("No complete html found"); + htmlClose(); + return; + } +int start; +for (start=end; start >= 0 && ! (startsWith("", lines[start]) || startsWith("", lines[start])) ; --start) + /* do nothing */ ; +if (start < 0) + { + htmlOpen("Background Status"); + errAbort("No html start tag found"); + htmlClose(); + return; + } +puts("Content-Type: text/html\n"); +int line; +boolean autoRefreshFound = FALSE; +boolean successfullyUploaded = FALSE; +for (line=start; line <= end; line++) + { + puts(lines[line]); + if (startsWith("setTimeout(function(){location = location;}", lines[line])) + autoRefreshFound = TRUE; + if (startsWith("Output has been successfully uploaded", lines[line])) + successfullyUploaded = TRUE; + } +// if it looks like the background is no longer running, +// include the .err stdout output for more informative problem message +char urlErr[512]; +char *textErr = NULL; +safef(urlErr, sizeof urlErr, "%s.err", url); +if (!autoRefreshFound && !successfullyUploaded && (fileSize(urlErr) > 0)) + { + readInGulp(urlErr, &textErr, NULL); + //printf("%s", textErr); /* DEBUG REMOVE? more annoying than helpful in some places. */ + } +} + + + +void startBackgroundWork(char *exec, char **pWorkUrl) +/* deal with forking off child for background work + * and setting up the trash file for communicating + * from the child to the browser */ +{ +char *workUrl = NULL; +char hgsid[64]; +struct tempName tn; +safef(hgsid, sizeof(hgsid), "%s", cartSessionId(cart)); +trashDirFile(&tn, "backGround", hgsid, ".tmp"); +workUrl = cloneString(tn.forCgi); +fflush(stdout); +fflush(stderr); +// seems that we need to use the double-fork trick +// to create enough separation between the non-waiting parent +// and the grand-child process. otherwise the OS and Apache are waiting on the child. + +int pid = fork(); +if (pid == -1) + { + errAbort("can't fork, error %d", errno); + } +if (pid == 0) // child + { + int pid2 = fork(); + if (pid2 == -1) + { + errAbort("can't fork, error %d", errno); + } + if (pid2 == 0) // grand child + { + + // we need to close or redup to open stdout, stderr, stdin + // in order for apache to break ties with it. + // Will the grandchild cgi still be able to function? + + // redirect stdout of child to the trash file for easier use of + // library functions that output html to stdout. + int out = mustOpenFd(tn.forCgi, O_WRONLY | O_CREAT); + fflush(stdout); + dup2(out,STDOUT_FILENO); /* closes STDOUT before setting it back to saved descriptor */ + close(out); + + // Unfortunately we must create our own stderr log file + char errName[1024]; + safef(errName, sizeof errName, "%s.err", tn.forCgi); + int err = mustOpenFd(errName, O_CREAT | O_WRONLY | O_APPEND); + dup2(err, STDERR_FILENO); + close(err); + + // stdin input is just empty + int in = mustOpenFd("/dev/null", O_RDONLY); + dup2(in, STDIN_FILENO); + close(in); + + // save own pid in .pid file + long selfPid = (long) getpid(); + char pidName[1024]; + safef(pidName, sizeof pidName, "%s.pid", tn.forCgi); + char pidBuf[64]; + safef(pidBuf, sizeof pidBuf, "%ld", selfPid); + int pidFd = mustOpenFd(pidName, O_CREAT | O_WRONLY | O_APPEND); + mustWriteFd(pidFd, pidBuf, strlen(pidBuf)); + close(pidFd); + + // progress is a separate channel for progress update + char progressName[1024]; + safef(progressName, sizeof progressName, "%s.progress", tn.forCgi); + + // execute so that we will be able to use database and other operations normally. + char execPath[4096]; + safef(execPath, sizeof execPath, "%s hgsid=%s backgroundProgress=%s", exec, hgsid, progressName); + char *args[10]; + int numArgs = chopString(execPath, " ", args, 10); + args[numArgs] = NULL; + // by creating a minimal environment and not inheriting from the parent, + // it cause cgiSpoof to run, picking up command-line params as cgi vars. + // SERVER_SOFTWARE triggers default cgi trash temp location + char *newenviron[] = { "HGDB_CONF=hg.conf", "SERVER_SOFTWARE=Apache", NULL }; + int sleepSeconds = 1; // was 5 + sleep(sleepSeconds); // Give the foreground process time to write the cart. + execve(args[0], args+1, newenviron); + // SHOULD NOT GET HERE UNLESS EXEC FAILED. + verbose(1,"execve failed for %s\n", exec); + _exit(0); // exit without the usual cleanup which messes up parent's db connections etc. + + } + else // child + { + _exit(0); // exit without the usual cleanup which messes up parent's db connections etc. + } + } +else // parent + { + *pWorkUrl = workUrl; + // wait for the exiting child (not grandchild) + int w, status; + do { + w = waitpid(pid, &status, WUNTRACED | WCONTINUED); + if (w == -1) + { + perror("waitpid"); + exit(EXIT_FAILURE); + } + + if (WIFEXITED(status)) + { + if (WEXITSTATUS(status) != 0) + verbose(1, "exited, status=%d\n", WEXITSTATUS(status)); + } + else if (WIFSIGNALED(status)) + { + verbose(1, "killed by signal %d\n", WTERMSIG(status)); + } + else if (WIFSTOPPED(status)) + { + verbose(1, "stopped by signal %d\n", WSTOPSIG(status)); + } + else if (WIFCONTINUED(status)) + { + verbose(1, "continued\n"); + } + } while (!WIFEXITED(status) && !WIFSIGNALED(status)); + + // done waiting for child. + + } + +} + + + +boolean isActiveDb(char *db) +/* check if the db is in dbDb and active */ +{ +struct sqlConnection *conn = hConnectCentral(); +char query[256]; +sqlSafef(query, sizeof query, "select count(*) from dbDb where name='%s' and active=1", db); +int count = sqlQuickNum(conn, query); +hDisconnectCentral(&conn); +return (count >= 1); +} + +unsigned isLiveHub(char *db, char **pUrl, boolean talkative) +{ +unsigned hubId = hubIdFromTrackName(db); +struct sqlConnection *conn = hConnectCentral(); +struct hubConnectStatus *hubStat = hubConnectStatusForId(conn, hubId); +hDisconnectCentral(&conn); +unsigned result = 0; +if (hubStat) + { + if (hubStat->errorMessage) + { + if (talkative) + printf("HUB errMessage=%s
\n", hubStat->errorMessage); + } + else + { + + if (hubStat->trackHub) + { + if (trackHubHasDatabase(hubStat->trackHub, db)) + { + if (pUrl) + *pUrl = cloneString(hubStat->hubUrl); + result = hubId; // indicate db found in hub. + } + if (talkative) + printf("[%s]
\n", hubStat->trackHub->shortLabel); + } + } + + } +return result; +} + +char *getHubUrlFromId(unsigned hubId) +{ +struct sqlConnection *conn = hConnectCentral(); +struct hubConnectStatus *hubStat = hubConnectStatusForId(conn, hubId); +hDisconnectCentral(&conn); +char *result = NULL; +if (hubStat) + { + if (!hubStat->errorMessage) + { + if (hubStat->trackHub) + { + result = cloneString(hubStat->hubUrl); + } + } + } +return result; +} + + +/* hold extra data that does not fit in the track struct itself, + * but which is track-specific */ +struct ctExtra + { + struct ctExtra *next; + char *name; // important to identity of the track in hgCustom + char *browserLines; + char *trackLine; + int tableRows; + unsigned long tableDataLength; + }; + +struct downloadResults + { + struct downloadResults *next; + char *db; + char *ctPath; // path to ct file in trash + struct customTrack *cts; // tracks + struct ctExtra *ctExtras; // parallel additional info to the tracks + }; + +struct downloadResults *processCtsForDownloadInternals(char *contents, char **pTrackHubsVar) +/* Process the saved session cart contents, + * looking for custom-tracks which are db-specific. */ +{ + +struct downloadResults *resultList = NULL; + +if (!contents) + return resultList; + +char *contentsToChop = cloneString(contents); +char *namePt = contentsToChop; + +struct sqlConnection *ctConn = hAllocConn(CUSTOM_TRASH); + +while (isNotEmpty(namePt)) + { + char *dataPt = strchr(namePt, '='); + char *nextNamePt; + if (dataPt == NULL) + errAbort("ERROR: Mangled session content string %s", namePt); + *dataPt++ = 0; + nextNamePt = strchr(dataPt, '&'); + if (nextNamePt != NULL) + *nextNamePt++ = 0; + if (startsWith(CT_FILE_VAR_PREFIX, namePt)) + { + cgiDecode(dataPt, dataPt, strlen(dataPt)); + + struct downloadResults *result = NULL; + AllocVar(result); + + char *db = namePt + strlen(CT_FILE_VAR_PREFIX); + result->db = cloneString(db); + + if (fileExists(dataPt)) + { + + result->ctPath = cloneString(dataPt); + + struct customTrack *cts = NULL, *track = NULL; + boolean thisGotLiveCT = FALSE, thisGotExpiredCT = FALSE; + + customFactoryTestExistence(db, dataPt, &thisGotLiveCT, &thisGotExpiredCT, &cts); + + result->cts = cts; + + // add it to a structure for tracking/printing later. + + for (track=cts; track; track = track->next) + { + struct ctExtra *extra = NULL; + AllocVar(extra); + + // what to save in ct output + + // name is important because hgCustom identifies by this string + char *name = hashFindVal(track->tdb->settingsHash, "name"); + extra->name = cloneString(name); + + char *browserLines = hashFindVal(track->tdb->settingsHash, "browserLines"); + // to handle multiple-lines replace semi-colon ";" with newline. + if (browserLines) + replaceChar(browserLines,';','\n'); + extra->browserLines = cloneString(browserLines); + + char *origTrackLine = hashFindVal(track->tdb->settingsHash, "origTrackLine"); + extra->trackLine = cloneString(origTrackLine); + + // is it weird that the loader customFactoryTestExistence() did not do this for me? + char *wibFilePath = hashFindVal(track->tdb->settingsHash, "wibFile"); + if (wibFilePath && fileExists(wibFilePath)) + { + track->wibFile = wibFilePath; + } + char *mafFilePath = hashFindVal(track->tdb->settingsHash, "mafFile"); + if (mafFilePath && fileExists(mafFilePath)) + { + track->wibFile = mafFilePath; + } + // old vcf type (not vcfTabix) + char *vcfFilePath = hashFindVal(track->tdb->settingsHash, "vcfFile"); + if (vcfFilePath && fileExists(vcfFilePath)) + { + track->wibFile = vcfFilePath; + } + + if (track->dbTrack && track->dbDataLoad && track->dbTableName) + { + extra->tableRows = sqlTableSizeIfExists(ctConn, track->dbTableName); + extra->tableDataLength = sqlTableDataSizeFromSchema(ctConn, CUSTOM_TRASH, track->dbTableName); + } + slAddHead(&result->ctExtras,extra); + } + slReverse(&result->ctExtras); + + } + + slAddHead(&resultList,result); + } + else if (sameString("trackHubs", namePt)) + { + cgiDecode(dataPt, dataPt, strlen(dataPt)); + if (pTrackHubsVar) + *pTrackHubsVar = cloneString(dataPt); + } + + namePt = nextNamePt; + } +hFreeConn(&ctConn); +freeMem(contentsToChop); + +slReverse(&resultList); +return resultList; + +} + +void processCtsForDownload(char *contents) +/* Process the saved session cart contents, + * looking for custom-tracks which are db-specific. */ +{ + +struct downloadResults *resultList = processCtsForDownloadInternals(contents, NULL); + +printf("

Custom Tracks

"); + +printf("You can backup the custom tracks which you previously uploaded to UCSC Genome Browser servers.
" + "It will create a single .tar.gz file with custom track data,
" + "which you can then download to your own machine for use as a backup.
" + "\n"); + +struct downloadResults *result = NULL; + + +int nonBigDataUrlCount = 0; +int ctCount = 0; +long totalDataToDownload = 0; + +for (result=resultList; result; result=result->next) + { + + if (!result->ctPath) // ct.bed was missing for the db + continue; + + printf("

Database %s

\n", result->db); + + if (startsWith("hub_", result->db)) + { + unsigned hubId = isLiveHub(result->db, NULL, TRUE); + if (hubId == 0) + { + printf("Skipping HUB Database %s. " + "This hub db is not currently available and will not get backed up.

\n", result->db); + continue; // skip db if not active. + } + } + else + { + if (!isActiveDb(result->db)) + { + printf("Skipping Database %s. " + "The db is not currently active and will not get backed up.

\n", result->db); + continue; // skip db if not active. + } + } + + struct customTrack *cts = result->cts, *track = NULL; + struct ctExtra *extras = result->ctExtras, *extra = NULL; + + printf("\n"); + + for (track=cts,extra=extras; track; track=track->next,extra=extra->next) + { + long trackDataToDownload = 0; + + ++ctCount; + + // handle user-friendly sizes GB MB KB and B + if (track->dbTrack && track->dbDataLoad && track->dbTableName) + { + char greek[32]; + ++nonBigDataUrlCount; + sprintWithGreekByte(greek, sizeof(greek), extra->tableDataLength); + trackDataToDownload += extra->tableDataLength; + + // handle wiggle cts which have an additional wig binary + // wibFile=../trash/ct/hgtct_genome_542_dc1750.wib + // wibFile=../trash/ct/ct_hgwdev_galt_e83f_3892d0.maf + // wibFIle=../trash/ct/ct_hgwdev_galt_4ba3_415cc0.vcf + if (track->wibFile && fileExists(track->wibFile)) + { + long wibFileSize = fileSize(track->wibFile); + char greek[32]; + sprintWithGreekByte(greek, sizeof(greek), wibFileSize); + trackDataToDownload += wibFileSize; + } + } + + // htmlFile=../trash/ct/ct_hgwdev_galt_d011_383c50.html + if (track->htmlFile && fileExists(track->htmlFile)) + { + long htmlFileSize = fileSize(track->htmlFile); + char greek[32]; + sprintWithGreekByte(greek, sizeof(greek), htmlFileSize); + trackDataToDownload += htmlFileSize; + } + + char greek[32]; + sprintWithGreekByte(greek, sizeof(greek), trackDataToDownload); + if (trackDataToDownload == 0) // suppress 0.0 B + greek[0] = 0; // empty string + printf("\n", extra->name, greek); // track name + + totalDataToDownload += trackDataToDownload; + } + + printf("
%s%s
\n"); + + } + +printf("
\n"); +printf("%d custom tracks found.
\n", ctCount); +printf("%d custom tracks stored at UCSC.
\n", nonBigDataUrlCount); + +char greek[32]; +sprintWithGreekByte(greek, sizeof(greek), totalDataToDownload); +printf("Total custom track data to backup: %s ", greek); + +cgiMakeButton(hgsMakeDownloadPrefix, "create custom tracks backup archive"); + +printf("
\n"); +printf("
\n"); + +} + + +void showDownloadSessionCtData(struct hashEl *downloadList) +/* Show download page for the given session */ +{ +char query[512]; +char **row = NULL; +struct sqlResult *sr = NULL; + +puts("Content-Type:text/html\n"); +cartWebStart(cart, NULL, "Backup Custom Tracks"); +jsInit(); + +struct sqlConnection *conn = hConnectCentral(); + +printf("
\n", + hgSessionName()); +cartSaveSession(cart); + + +sqlSafef(query, sizeof(query), "SELECT firstUse, contents from %s " + "WHERE id=%u", + "sessionDb", cartSessionRawId(cart)); + +sr = sqlGetResult(conn, query); + +if ((row = sqlNextRow(sr)) != NULL) + { + char *db = NULL; + char *firstUse = row[0]; + + printf("
"); + printf("\n"); + printf("" + "" + "" + ""); + printf("\n"); + + printf(""); + + printf("", firstUse); + + char *contents = row[1]; + + char *dbIdx = NULL; + if (startsWith("db=", contents)) // the first variable in the cart (rare case) + dbIdx = contents+3; + else + dbIdx = strstr(contents, "&db=") + 4; + + if (dbIdx) + { + char *dbEnd = strchr(dbIdx, '&'); + if (dbEnd != NULL) + db = cloneStringZ(dbIdx, dbEnd-dbIdx); + else + db = cloneString(dbIdx); + } + else + db = cloneString("n/a"); + printf("", db); + + printf(""); + + printf("\n"); + + printf("
created onassembly
%s  %s
\n"); + + printf("
\n"); + printf("

\n"); + + processCtsForDownload(contents); + + } +sqlFreeResult(&sr); + +hDisconnectCentral(&conn); +printf("
\n"); + +cartWebEnd(); + +} + +void makeTrashFileLink(char *trashFile, char *cwd, char *outDbDir, char *name) +/* Touch file and make a symlink to it as db/name.ext */ +{ +// touch to refresh access time so it will not be cleaned out +// this may be redundant in the future but also harmless +readAndIgnore(trashFile); + +// make a symlink with name.ext using track name and wibFile extension +// that points to the original wibFile +char newPath[1024]; +char toDir[PATH_LEN], toFile[FILENAME_LEN], toExt[FILEEXT_LEN]; +splitPath(trashFile, toDir, toFile, toExt); + +// record the original path in a file. +safef(newPath, sizeof newPath, "%s/%s%s", outDbDir, name, toExt); + +char *oldPath = expandRelativePath(cwd, trashFile); + +if (symlink(oldPath, newPath)) + errAbort("failed to symlink from %s to %s", oldPath, newPath); +} + +void saveSqlDataForTable(char *tableName, FILE *f) +/* append table data as tab-separated text */ +{ +struct sqlConnection *conn = sqlConnect(CUSTOM_TRASH); +char query[256]; +struct sqlResult *sr; +char **row = NULL; + +sqlSafef(query, sizeof(query), "select * from %s", tableName); +sr = sqlGetResult(conn, query); +int numFields = sqlCountColumns(sr); +boolean hasBin = (sqlFieldColumn(sr, "bin") != -1); +struct dyString *dy = dyStringNew(1024); +while ((row=sqlNextRow(sr))) + { + int start = 0; + if (hasBin) // skip bin column + ++start; + int i; + for (i=start; i start) + dyStringAppendC(dy, '\t'); + dyStringAppend(dy, row[i]); + } + dyStringAppendC(dy, '\n'); + + mustWrite(f, dy->string, dy->stringSize); + + dyStringClear(dy); + } +sqlFreeResult(&sr); +sqlDisconnect(&conn); +} + +void updateProgessFile(char *backgroundProgress, struct dyString *dy) +/* update the progress file as a separate channel */ +{ +// write to new file +char newName[1024]; +safef(newName, sizeof newName, "%s.new", backgroundProgress); +FILE *f = mustOpen(newName, "w"); +mustWrite(f, dy->string, dy->stringSize); +carefulClose(&f); +// replace original +if (rename(newName, backgroundProgress)) + errnoAbort("failed to rename progress file"); +} + +void appendTrashFileToCt(FILE *fct, char *fileName) +/* append wibFile to open fct file */ +{ +FILE *f = mustOpen(fileName, "r"); +long remaining = fileSize(fileName); +int bufSize = 65536; +char *buf = needMem(bufSize); +while (remaining) + { + int bufRemain = bufSize; + if (bufRemain > remaining) + bufRemain = remaining; + mustRead(f, buf, bufRemain); // mustRead OK since we are reading from disk + mustWrite(fct, buf, bufRemain); + remaining -= bufRemain; + } +carefulClose(&f); +} + +void doOutWigData(char *table, char *database, char *outFile) // should be CUSTOM_TRASH +/* Return wiggle data in variableStep format. */ +{ +/* Write out wig data in region. */ + +struct wiggleDataStream *wds = NULL; +int operations = wigFetchAscii; + +operations = wigFetchAscii; + +wds = wiggleDataStreamNew(); + +wds->getData(wds, database, table, operations); + +wds->asciiOut(wds, database, outFile, TRUE, FALSE); + +wiggleDataStreamFree(&wds); + +} + +void makeDownloadSessionCtData(char *param1, char *backgroundProgress) +/* Download tables and data to save save in compressed archive. */ +{ +char query[512]; +char **row = NULL; +struct sqlResult *sr = NULL; + + +// Initialize .progress channel file +struct dyString *dyProg = newDyString(256); +dyStringPrintf(dyProg, "please wait, dumping data to archive ...
\n"); +updateProgessFile(backgroundProgress, dyProg); +lazarusLives(20 * 60); + +htmlOpen("Preparing Custom Tracks Backup Archive for Download"); +jsInit(); + +struct sqlConnection *conn = hConnectCentral(); + +printf("
\n", + hgSessionName()); +cartSaveSession(cart); + +sqlSafef(query, sizeof(query), "SELECT contents from %s " + "WHERE id=%u", + "sessionDb", cartSessionRawId(cart)); + +sr = sqlGetResult(conn, query); + +// setup temp output dir: +// ../trash/ +// ctBackup/ +// random@#$%/ # random value so it is unique + +/* make a directory, including parent directories */ + +char tempOut[1024]; +safef(tempOut, sizeof tempOut, "../trash/ctBackup"); +if (!fileExists(tempOut)) + { + makeDir(tempOut); + } + +char tempOutRand[1024]; +// make randomDir +char *randPath=NULL; +int randCount = 0; +while(TRUE) + { + randPath=makeRandomKey(128+33); // at least 128 bits of protection, 33 for the world population size. + // Avoid the possibility of somehow doing more than one instance of the session + // backup at the same time. + // On the extremely rare chance that the directory already exists, + // just call the function again. + safef(tempOutRand, sizeof tempOutRand, "%s/%s", tempOut, randPath); + if (makeDir(tempOutRand)) + break; + ++randCount; + if (randCount > 100) // should never happen + errAbort("unable to create random output dir."); + } + +int foundCount = 0; +if ((row = sqlNextRow(sr)) != NULL) + { + ++foundCount; + + char *contents = row[0]; + + + char *trackHubsVar = NULL; + + struct downloadResults *resultList = processCtsForDownloadInternals(contents, &trackHubsVar); + + struct downloadResults *result = NULL; + + for (result=resultList; result; result=result->next) + { + struct customTrack *cts = result->cts, *track = NULL; + struct ctExtra *extras = result->ctExtras, *extra = NULL; + + // empty db dir can trigger cleaning out of cart ct var for db + // if it has no ct.bed + char outDbDir[1024]; + safef(outDbDir, sizeof outDbDir, "%s/%s", tempOutRand, result->db); + if (!fileExists(outDbDir)) + { + makeDir(outDbDir); + } + + if (!result->ctPath) // ct.bed was missing for the db + continue; + + char *hubUrl = NULL; + + if (startsWith("hub_", result->db)) + { + unsigned hubId = isLiveHub(result->db, &hubUrl, FALSE); + if (hubId == 0) + { + continue; // skip hub if not active. + } + } + else + { + if (!isActiveDb(result->db)) + { + continue; // skip db if not active. + } + } + + + printf("

%s

\n", result->db); + dyStringPrintf(dyProg,"

%s

\n", result->db); + updateProgessFile(backgroundProgress, dyProg); + lazarusLives(20 * 60); + + + if (startsWith("hub_", result->db)) + { + // Save the hubUrl now in case it gets restored on another machine + // that does NOT have that hub loaded on it. + // save output as hubUrl text file + char hubUrlPath[1024]; + safef(hubUrlPath, sizeof hubUrlPath, "%s/hubUrl", outDbDir); + FILE *f = mustOpen(hubUrlPath, "w"); + fprintf(f, "%s", hubUrl); // NO NEWLINE + carefulClose(&f); + } + + char cwd[PATH_LEN]; + if (!getcwd(cwd, sizeof(cwd))) + errnoAbort("unable to get current dir."); + + for (track=cts,extra=extras; track; track=track->next,extra=extra->next) + { + + printf("%s
\n", extra->name); + + dyStringPrintf(dyProg, "%s
\n", extra->name); + updateProgessFile(backgroundProgress, dyProg); + lazarusLives(20 * 60); + + char outNameCt[2014]; + safef(outNameCt, sizeof outNameCt, "%s/%s.ct", outDbDir, extra->name); + FILE *fct = mustOpen(outNameCt, "w"); + + // write the track header + if (extra->browserLines) + fprintf(fct, "%s", extra->browserLines); // should have ; converted \n already + fprintf(fct, "%s\n", extra->trackLine); + + boolean wibMissing = track->wibFile && !fileExists(track->wibFile); + + if (track->dbTrack && track->dbDataLoad && track->dbTableName && !wibMissing) + { + + //printf("%s
\n", track->wibFile); // DEBUG REMOVE + + + // handle wiggle cts which have an additional wig binary + // wibFile='../trash/ct/hgtct_genome_542_dc1750.wib' + // wibFile='../trash/ct/ct_hgwdev_galt_e83f_3892d0.maf' + // wibFile='../trash/ct/ct_hgwdev_galt_4ba3_415cc0.vcf' + // symlink for speed, file should not change + if (track->wibFile) + { + if (endsWith(track->wibFile, ".wib")) + { + // dump wig as ascii + char outNameWig[2014]; + safef(outNameWig, sizeof outNameWig, "%s/%s.wig", outDbDir, extra->name); + doOutWigData(track->dbTableName, CUSTOM_TRASH, outNameWig); // no easy way to append it to .ct directly + // append text to ct + appendTrashFileToCt(fct, outNameWig); + remove(outNameWig); + } + if (endsWith(track->wibFile, ".maf")) + { + // append text to ct + appendTrashFileToCt(fct, track->wibFile); + } + if (endsWith(track->wibFile, ".vcf")) + { + // append text to ct + appendTrashFileToCt(fct, track->wibFile); + } + } + else // simple BED-like + { + saveSqlDataForTable(track->dbTableName, fct); + } + + // handle extra htmlFile track doc + // htmlFile=../trash/ct/ct_hgwdev_galt_d011_383c50.html + // symlink for speed, file should not change + if (track->htmlFile && fileExists(track->htmlFile)) + { + makeTrashFileLink(track->htmlFile, cwd, outDbDir, extra->name); + } + } + + carefulClose(&fct); + } + } + + } +sqlFreeResult(&sr); + +if (foundCount == 0) + errAbort("No session found for hgsid=%u", cartSessionRawId(cart)); + +char archiveName[1024]; +safef(archiveName, sizeof archiveName, "savedSessionCtRaw.tar.gz"); + +dyStringPrintf(dyProg, "
\n"); +int saveDySize = dyProg->stringSize; +dyStringPrintf(dyProg, "creating and compressing archive %s
\n", archiveName); +updateProgessFile(backgroundProgress, dyProg); +lazarusLives(20 * 60); + +// create the archive +char cmd[2048]; +safef(cmd, sizeof cmd, "cd %s; tar -cpzhf %s *", tempOutRand, archiveName); +mustSystem(cmd); + +dyProg->stringSize = saveDySize; // restore prev size, popping. +dyProg->string[dyProg->stringSize] = 0; +dyStringPrintf(dyProg, "archive %s created
\n", archiveName); +updateProgessFile(backgroundProgress, dyProg); +lazarusLives(20 * 60); + +char downPath[1024]; +safef(downPath, sizeof downPath, "%s/%s", tempOutRand, archiveName); + +printf("
\n"); + +char *encDownPath = cgiEncodeFull(downPath); +char downButName[1024]; +safef(downButName, sizeof(downButName), "%s%s", hgsDoDownloadPrefix, encDownPath); + +// using getElementById('%s') here is a work-around for special charachters in id. +// I tried escaping, but it did not seem to work with our jQuery version. +// mainly the special characters it sees are periods and slashes. +char downButNameHack[1024]; +safef(downButNameHack, sizeof(downButNameHack), "document.getElementById('%s')", downButName); + +printf("Download archive as local file:\n"); +cgiMakeOnKeypressTextVar(hgsSaveLocalBackupFileName, + cartUsualString(cart, hgsSaveLocalBackupFileName, ""), + 20, jsPressOnEnter(downButNameHack)); +printf(".tar.gz"); + +char greek[32]; +sprintWithGreekByte(greek, sizeof(greek), fileSize(downPath)); +printf(" %s ", greek); + +printf("", downButName, downButName); + +// check the file name, make sure it is not blank +char js[1024]; +safef(js, sizeof js, +"var textVar = document.getElementById('%s');" +"if (textVar.value != '')" +" return true;" +"alert('Please enter a filename for the backup archive');" +"return false;" +, hgsSaveLocalBackupFileName); + +jsOnEventById("click", downButName, js); + +// Add a button that just returns to the saved sessions list. +// because after pressing download and it will go into the users downloads folder, +// the browser remains sitting on this page and needs a way to return +// to the saved sessions list. +printf("   "); +printf(""); + +printf("
\n"); +printf("
\n"); + +hDisconnectCentral(&conn); +printf("
\n"); + +htmlClose(); +fflush(stdout); + +} + +void doDownloadSessionCtData(struct hashEl *downloadPathList) +/* Download given table to browser to save. */ +{ + +struct hashEl *hel = NULL; + +// I think it should only get one at a time, not a list. +hel = downloadPathList; + +char *encDownPath = hel->name + strlen(hgsDoDownloadPrefix); +char *downPath = cgiDecodeClone(encDownPath); + +char *fileName = cartString(cart, hgsSaveLocalBackupFileName); + +char outFile[1024]; +safef(outFile, sizeof outFile, "%s.tar.gz", fileName); + +long fSize = fileSize(downPath); + +printf("Content-Type: application/octet-stream\n"); +printf("Content-Disposition: attachment; filename=\"%s\"\n", outFile); +printf("Content-Length: %ld\n", fSize); +printf("\n"); + +FILE *f = mustOpen(downPath, "r"); +long remaining = fSize; +int bufSize = 65536; +char *buf = needMem(bufSize); +while (remaining) + { + int bufRemain = bufSize; + if (bufRemain > remaining) + bufRemain = remaining; + mustRead(f, buf, bufRemain); // mustRead OK since we are reading from disk + mustWrite(stdout, buf, bufRemain); + remaining -= bufRemain; + lazarusLives(20 * 60); // extend keep-alive time. for big downloads on slow connections. + } +carefulClose(&f); + +} +