95433400da33b5bae4c4c8475f1305fc08510f66 jcasper Sat Jun 11 14:25:14 2016 -0700 Store the sort/filter state of jQuery-DataTables-enabled tables in hgSession and hgPublicSessions to the cart, without including that state in saved sessions. Block IE versions before 11 from seeing the DataTables table version, due to checkbox issues. Bonus: fix for a minor session deletion bug. refs #15312, #17509 diff --git src/hg/lib/cart.c src/hg/lib/cart.c index 2074c8c..2b43a38 100644 --- src/hg/lib/cart.c +++ src/hg/lib/cart.c @@ -1,2904 +1,2920 @@ /* Copyright (C) 2014 The Regents of the University of California * See README in this or parent directory for licensing information. */ #include "common.h" #include "hCommon.h" #include "obscure.h" #include "linefile.h" #include "errAbort.h" #include "hash.h" #include "cheapcgi.h" #include "cartDb.h" #include "htmshell.h" #include "hgConfig.h" #include "cart.h" #include "net.h" #include "web.h" #include "hdb.h" #include "jksql.h" #include "jsHelper.h" #include "trashDir.h" #ifndef GBROWSE #include "customFactory.h" #include "googleAnalytics.h" #include "wikiLink.h" #endif /* GBROWSE */ #include "hgMaf.h" #include "hui.h" #include "geoMirror.h" #include "hubConnect.h" #include "trackHub.h" #include "cgiApoptosis.h" static char *sessionVar = "hgsid"; /* Name of cgi variable session is stored in. */ static char *positionCgiName = "position"; DbConnector cartDefaultConnector = hConnectCart; DbDisconnect cartDefaultDisconnector = hDisconnectCart; static void hashUpdateDynamicVal(struct hash *hash, char *name, void *val) /* Val is a dynamically allocated (freeMem-able) entity to put * in hash. Override existing hash item with that name if any. * Otherwise make new hash item. */ { struct hashEl *hel = hashLookup(hash, name); if (hel == NULL) hashAdd(hash, name, val); else { freeMem(hel->val); hel->val = val; } } static struct dyString *hubWarnDy; void cartHubWarn(char *format, va_list args) /* save up hub related warnings to put out later */ { char warning[1024]; vsnprintf(warning,sizeof(warning),format, args); if (hubWarnDy == NULL) hubWarnDy = newDyString(100); dyStringPrintf(hubWarnDy, "%s\n", warning); } void cartFlushHubWarnings() /* flush the hub warning (if any) */ { if (hubWarnDy) warn("%s",hubWarnDy->string); } void cartTrace(struct cart *cart, char *when, struct sqlConnection *conn) /* Write some properties of the cart to stderr for debugging. */ { if (cfgOption("cart.trace") == NULL) return; struct cartDb *u = cart->userInfo, *s = cart->sessionInfo; char *pix = hashFindVal(cart->hash, "pix"); char *textSize = hashFindVal(cart->hash, "textSize"); char *trackControls = hashFindVal(cart->hash, "trackControlsOnMain"); int uLen, sLen; if (conn != NULL) { /* Since the content string is chopped, query for the actual length. */ struct dyString *query = dyStringNew(1024); sqlDyStringPrintf(query, "select length(contents) from %s" " where id = %d", userDbTable(), u->id); if (cartDbUseSessionKey()) sqlDyStringPrintf(query, " and sessionKey='%s'", u->sessionKey); uLen = sqlQuickNum(conn, query->string); dyStringClear(query); sqlDyStringPrintf(query, "select length(contents) from %s" " where id = %d", sessionDbTable(),s->id); if (cartDbUseSessionKey()) sqlDyStringPrintf(query, " and sessionKey='%s'", s->sessionKey); sLen = sqlQuickNum(conn, query->string); dyStringFree(&query); } else { uLen = strlen(u->contents); sLen = strlen(s->contents); } if (pix == NULL) pix = "-"; if (textSize == NULL) textSize = "-"; if (trackControls == NULL) trackControls = "-"; fprintf(stderr, "ASH: %22s: " "u.i=%d u.l=%d u.c=%d s.i=%d s.l=%d s.c=%d " "p=%s f=%s t=%s pid=%ld %s\n", when, u->id, uLen, u->useCount, s->id, sLen, s->useCount, pix, textSize, trackControls, (long)getpid(), cgiRemoteAddr()); char userIdKey[256]; cartDbSecureId(userIdKey, sizeof userIdKey, u); if (cart->userId && !sameString(userIdKey, cart->userId)) fprintf(stderr, "ASH: bad userId %s --> %d_%s! pid=%ld\n", cart->userId, u->id, u->sessionKey, (long)getpid()); char sessionIdKey[256]; cartDbSecureId(sessionIdKey, sizeof sessionIdKey, s); if (cart->sessionId && !sameString(sessionIdKey, cart->sessionId)) fprintf(stderr, "ASH: bad sessionId %s --> %d_%s! pid=%ld\n", cart->sessionId, s->id, s->sessionKey, (long)getpid()); } boolean cartTablesOk(struct sqlConnection *conn) /* Return TRUE if cart tables are accessible (otherwise, the connection * doesn't do us any good). */ { if (!sqlTableExists(conn, userDbTable())) { fprintf(stderr, "ASH: cartTablesOk failed on %s.%s! pid=%ld\n", sqlGetDatabase(conn), userDbTable(), (long)getpid()); return FALSE; } if (!sqlTableExists(conn, sessionDbTable())) { fprintf(stderr, "ASH: cartTablesOk failed on %s.%s! pid=%ld\n", sqlGetDatabase(conn), sessionDbTable(), (long)getpid()); return FALSE; } return TRUE; } void cartParseOverHash(struct cart *cart, char *contents) /* Parse cgi-style contents into a hash table. This will *not* * replace existing members of hash that have same name, so we can * support multi-select form inputs (same var name can have multiple * values which will be in separate hashEl's). */ { struct hash *hash = cart->hash; char *namePt, *dataPt, *nextNamePt; namePt = contents; while (namePt != NULL && namePt[0] != 0) { dataPt = strchr(namePt, '='); if (dataPt == NULL) errAbort("Mangled input string %s", namePt); *dataPt++ = 0; nextNamePt = strchr(dataPt, '&'); if (nextNamePt == NULL) nextNamePt = strchr(dataPt, ';'); /* Accomodate DAS. */ if (nextNamePt != NULL) *nextNamePt++ = 0; cgiDecode(dataPt,dataPt,strlen(dataPt)); hashAdd(hash, namePt, cloneString(dataPt)); namePt = nextNamePt; } } static boolean looksCorrupted(struct cartDb *cdb) /* Test for db corruption by checking format of firstUse field. */ { if (cdb == NULL) return FALSE; else { char *words[3]; int wordCount = 0; boolean isCorr = FALSE; char *fu = cloneString(cdb->firstUse); wordCount = chopByChar(fu, '-', words, ArraySize(words)); if (wordCount < 3) isCorr = TRUE; else { time_t theTime = time(NULL); struct tm *tm = localtime(&theTime); int year = atoi(words[0]); int month = atoi(words[1]); if ((year < 2000) || (year > (1900+tm->tm_year)) || (month < 1) || (month > 12)) isCorr = TRUE; } freez(&fu); return isCorr; } } struct cartDb *cartDbLoadFromId(struct sqlConnection *conn, char *table, char *secureId) /* Load up cartDb entry for particular ID. Returns NULL if no such id. */ { if (!secureId) return NULL; else { struct cartDb *cdb = NULL; struct dyString *where = dyStringNew(256); char *sessionKey = NULL; unsigned int id = cartDbParseId(secureId, &sessionKey); sqlDyStringPrintfFrag(where, "id = %u", id); if (cartDbUseSessionKey()) { if (!sessionKey) sessionKey = ""; sqlDyStringPrintfFrag(where, " and sessionKey='%s'", sessionKey); } cdb = cartDbLoadWhere(conn, table, where->string); dyStringFree(&where); if (looksCorrupted(cdb)) { /* Can't use warn here -- it interrupts the HTML header, causing an * err500 (and nothing useful in error_log) instead of a warning. */ fprintf(stderr, "%s id=%u looks corrupted -- starting over with new %s id.\n", table, id, table); cdb = NULL; } return cdb; } } struct cartDb *loadDb(struct sqlConnection *conn, char *table, char *secureId, boolean *found) /* Load bits from database and save in hash. */ { struct cartDb *cdb; boolean result = TRUE; cdb = cartDbLoadFromId(conn, table, secureId); if (!cdb) { result = FALSE; struct dyString *query = dyStringNew(256); sqlDyStringPrintf(query, "INSERT %s VALUES(0,'',0,now(),now(),0", table); char *sessionKey = ""; if (cartDbHasSessionKey(conn, table)) { if (cartDbUseSessionKey()) { sessionKey = cartDbMakeRandomKey(128+33); // at least 128 bits of protection, 33 for the world population size. } sqlDyStringPrintf(query, ",'%s'", sessionKey); } sqlDyStringPrintf(query, ")"); sqlUpdate(conn, query->string); dyStringFree(&query); unsigned int id = sqlLastAutoId(conn); char newSecureId[256]; if (cartDbUseSessionKey() && !sameString(sessionKey,"")) safef(newSecureId, sizeof newSecureId, "%u_%s", id, sessionKey); else safef(newSecureId, sizeof newSecureId, "%u", id); if ((cdb = cartDbLoadFromId(conn,table,newSecureId)) == NULL) errAbort("Couldn't get cartDb for id=%u right after loading. " "MySQL problem??", id); if (!sameString(sessionKey,"")) freeMem(sessionKey); } *found = result; return cdb; } void cartExclude(struct cart *cart, char *var) /* Exclude var from persistent storage. */ { hashAdd(cart->exclude, var, NULL); } void sessionTouchLastUse(struct sqlConnection *conn, char *encUserName, char *encSessionName) /* Increment namedSessionDb.useCount and update lastUse for this session. */ { struct dyString *dy = dyStringNew(1024); int useCount; sqlDyStringPrintf(dy, "SELECT useCount FROM %s " "WHERE userName = '%s' AND sessionName = '%s';", namedSessionTable, encUserName, encSessionName); useCount = sqlQuickNum(conn, dy->string) + 1; dyStringClear(dy); sqlDyStringPrintf(dy, "UPDATE %s SET useCount = %d, lastUse=now() " "WHERE userName = '%s' AND sessionName = '%s';", namedSessionTable, useCount, encUserName, encSessionName); sqlUpdate(conn, dy->string); dyStringFree(&dy); } #ifndef GBROWSE void cartCopyCustomTracks(struct cart *cart) /* If cart contains any live custom tracks, save off a new copy of them, * to prevent clashes by multiple uses of the same session. */ { struct hashEl *el, *elList = hashElListHash(cart->hash); for (el = elList; el != NULL; el = el->next) { if (startsWith(CT_FILE_VAR_PREFIX, el->name)) { char *db = &el->name[strlen(CT_FILE_VAR_PREFIX)]; struct slName *browserLines = NULL; struct customTrack *ctList = NULL; char *ctFileName = (char *)(el->val); if (fileExists(ctFileName)) ctList = customFactoryParseAnyDb(db, ctFileName, TRUE, &browserLines); /* Save off only if the custom tracks are live -- if none are live, * leave cart variables in place so hgSession can detect and inform * the user. */ if (ctList) { struct customTrack *ct; static struct tempName tn; char *ctFileVar = el->name; char *ctFileName; for (ct = ctList; ct != NULL; ct = ct->next) { copyFileToTrash(&(ct->htmlFile), "ct", CT_PREFIX, ".html"); copyFileToTrash(&(ct->wibFile), "ct", CT_PREFIX, ".wib"); copyFileToTrash(&(ct->wigFile), "ct", CT_PREFIX, ".wig"); } trashDirFile(&tn, "ct", CT_PREFIX, ".bed"); ctFileName = tn.forCgi; cartSetString(cart, ctFileVar, ctFileName); customTracksSaveFile(db, ctList, ctFileName); } } } } #endif /* GBROWSE */ static void storeInOldVars(struct cart *cart, struct hash *oldVars, char *var) /* Store all cart hash elements for var into oldVars (if it exists). */ { if (oldVars == NULL) return; struct hashEl *hel = hashLookup(cart->hash, var); // NOTE: New cgi vars not in old cart cannot be distinguished from vars not newly set by cgi. // Solution: Add 'empty' var to old vars for cgi vars not already in cart if (hel == NULL) hashAdd(oldVars, var, cloneString(CART_VAR_EMPTY)); while (hel != NULL) { hashAdd(oldVars, var, cloneString(hel->val)); hel = hashLookupNext(hel); } } static void cartJustify(struct cart *cart, struct hash *oldVars) /* Handles rules for allowing some cart settings to override others. */ { // New priority (position) settings should erase imgOrd settings if (oldVars == NULL) return; #define POSITION_SUFFIX ".priority" #define IMGORD_SUFFIX "_imgOrd" struct hashEl *list = NULL, *el; list = hashElListHash(cart->hash); //warn("cartJustify() begins"); for (el = list; el != NULL; el = el->next) { if (endsWith(el->name,POSITION_SUFFIX)) { if (cartValueHasChanged(cart,oldVars,el->name,TRUE,TRUE)) { int suffixOffset = strlen(el->name) - strlen(POSITION_SUFFIX); if (suffixOffset>0) { char *name = cloneString(el->name); safecpy(name+suffixOffset,strlen(POSITION_SUFFIX),IMGORD_SUFFIX); // We know that POSITION_SUFFIX is longer than IMGORD_SUFFIX cartRemove(cart, name); // Removes if found freeMem(name); } } } } } static void loadCgiOverHash(struct cart *cart, struct hash *oldVars) /* Store CGI variables in cart. */ { struct cgiVar *cv, *cvList = cgiVarList(); char *booShadow = cgiBooleanShadowPrefix(); int booSize = strlen(booShadow); char *multShadow = cgiMultListShadowPrefix(); int multSize = strlen(multShadow); struct hash *booHash = newHash(8); struct hash *cgiHash = hashNew(11); /* First handle boolean variables and store in cgiHash. We store in a * separate hash in order to distinguish between a list-variable's old * values in the cart hash and new values from cgi. */ for (cv = cvList; cv != NULL; cv = cv->next) { if (startsWith(booShadow, cv->name)) { char *booVar = cv->name + booSize; // Support for 2 boolean CBs: checked/unchecked (1/0) and enabled/disabled:(-1/-2) char *val = (cgiVarExists(booVar) ? "1" : cv->val); storeInOldVars(cart, oldVars, booVar); cartRemove(cart, booVar); hashAdd(cgiHash, booVar, val); hashAdd(booHash, booVar, NULL); } else if (startsWith(multShadow, cv->name)) { /* This shadow variable enables us to detect when all inputs in * the multi-select box have been deselected. */ char *multVar = cv->name + multSize; if (! cgiVarExists(multVar)) { storeInOldVars(cart, oldVars, multVar); storeInOldVars(cart, oldVars, cv->name); cartRemove(cart, multVar); } } } /* Handle non-boolean vars. */ for (cv = cgiVarList(); cv != NULL; cv = cv->next) { if (! (startsWith(booShadow, cv->name) || hashLookup(booHash, cv->name))) { storeInOldVars(cart, oldVars, cv->name); cartRemove(cart, cv->name); if (differentString(cv->val, CART_VAR_EMPTY)) // NOTE: CART_VAR_EMPTY logic not implemented for boolShad hashAdd(cgiHash, cv->name, cv->val); } } /* Add new settings to cart (old values of these variables have been * removed above). */ struct hashEl *hel = hashElListHash(cgiHash); while (hel != NULL) { cartAddString(cart, hel->name, hel->val); hel = hel->next; } hashFree(&cgiHash); hashFree(&booHash); } static void hashEmpty(struct hash *hash) /* Remove everything from hash. */ { struct hashEl *hel, *helList = hashElListHash(hash); for (hel = helList; hel != NULL; hel = hel->next) { freez(&(hel->val)); hashRemove(hash, hel->name); } hashElFreeList(&helList); assert(hashNumEntries(hash) == 0); } #ifndef GBROWSE void cartLoadUserSession(struct sqlConnection *conn, char *sessionOwner, char *sessionName, struct cart *cart, struct hash *oldVars, char *actionVar) /* If permitted, load the contents of the given user's session, and then * reload the CGI settings (to support override of session settings). * If non-NULL, oldVars will contain values overloaded when reloading CGI. * If non-NULL, actionVar is a cartRemove wildcard string specifying the * CGI action variable that sent us here. */ { struct sqlResult *sr = NULL; char **row = NULL; char *userName = wikiLinkUserName(); char *encSessionName = cgiEncodeFull(sessionName); char *encSessionOwner = cgiEncodeFull(sessionOwner); char query[512]; if (isEmpty(sessionOwner)) errAbort("Please go back and enter a wiki user name for this session."); if (isEmpty(sessionName)) errAbort("Please go back and enter a session name to load."); sqlSafef(query, sizeof(query), "SELECT shared, contents FROM %s " "WHERE userName = '%s' AND sessionName = '%s';", namedSessionTable, encSessionOwner, encSessionName); sr = sqlGetResult(conn, query); if ((row = sqlNextRow(sr)) != NULL) { boolean shared = atoi(row[0]); if (shared || (userName && sameString(sessionOwner, userName))) { char *sessionVar = cartSessionVarName(); char *hgsid = cartSessionId(cart); + char *sessionTableString = cartOptionalString(cart, hgSessionTableState); + sessionTableString = cloneString(sessionTableString); + char *pubSessionsTableString = cartOptionalString(cart, hgPublicSessionsTableState); + pubSessionsTableString = cloneString(pubSessionsTableString); struct sqlConnection *conn2 = hConnectCentral(); sessionTouchLastUse(conn2, encSessionOwner, encSessionName); cartRemoveLike(cart, "*"); cartParseOverHash(cart, row[1]); cartSetString(cart, sessionVar, hgsid); + if (sessionTableString != NULL) + cartSetString(cart, hgSessionTableState, sessionTableString); + if (pubSessionsTableString != NULL) + cartSetString(cart, hgPublicSessionsTableState, pubSessionsTableString); if (oldVars) hashEmpty(oldVars); /* Overload settings explicitly passed in via CGI (except for the * command that sent us here): */ loadCgiOverHash(cart, oldVars); if (isNotEmpty(actionVar)) cartRemove(cart, actionVar); hDisconnectCentral(&conn2); } else errAbort("Sharing has not been enabled for user %s's session %s.", sessionOwner, sessionName); } else errAbort("Could not find session %s for user %s.", sessionName, sessionOwner); sqlFreeResult(&sr); freeMem(encSessionName); } #endif /* GBROWSE */ void cartLoadSettings(struct lineFile *lf, struct cart *cart, struct hash *oldVars, char *actionVar) /* Load settings (cartDump output) into current session, and then * reload the CGI settings (to support override of session settings). * If non-NULL, oldVars will contain values overloaded when reloading CGI. * If non-NULL, actionVar is a cartRemove wildcard string specifying the * CGI action variable that sent us here. */ { char *line = NULL; int size = 0; char *sessionVar = cartSessionVarName(); char *hgsid = cartSessionId(cart); - +char *sessionTableString = cartOptionalString(cart, hgSessionTableState); +sessionTableString = cloneString(sessionTableString); +char *pubSessionsTableString = cartOptionalString(cart, hgPublicSessionsTableState); +pubSessionsTableString = cloneString(pubSessionsTableString); cartRemoveLike(cart, "*"); cartSetString(cart, sessionVar, hgsid); +if (sessionTableString != NULL) + cartSetString(cart, hgSessionTableState, sessionTableString); +if (pubSessionsTableString != NULL) + cartSetString(cart, hgPublicSessionsTableState, pubSessionsTableString); + while (lineFileNext(lf, &line, &size)) { char *var = nextWord(&line); if (isEmpty(var)) // blank line continue; char *val = line; if (sameString(var, sessionVar)) continue; else { if (val != NULL) { struct dyString *dy = dyStringSub(val, "\\n", "\n"); cartAddString(cart, var, dy->string); dyStringFree(&dy); } else if (var != NULL) { cartSetString(cart, var, ""); } } /* not hgsid */ } /* each line */ if (oldVars) hashEmpty(oldVars); /* Overload settings explicitly passed in via CGI (except for the * command that sent us here): */ loadCgiOverHash(cart, oldVars); if (isNotEmpty(actionVar)) cartRemove(cart, actionVar); } static char *now() /* Return a mysql-formatted time like "2008-05-19 15:33:34". */ { char nowBuf[256]; time_t seconds = clock1(); struct tm *theTime = localtime(&seconds); strftime(nowBuf, sizeof nowBuf, "%Y-%m-%d %H:%M:%S", theTime); return cloneString(nowBuf); } static struct cartDb *emptyCartDb() /* Create a new empty placeholder cartDb. */ { struct cartDb *cdb; AllocVar(cdb); cdb->contents = cloneString(""); cdb->firstUse = now(); cdb->lastUse = now(); cdb->useCount = 1; // TODO does anything need to go here for sessionKey? maybe not since id is not set here. return cdb; } struct cart *cartFromHash(struct hash *hash) /* Create a cart from hash */ { struct cart *cart; AllocVar(cart); cart->hash = hash; cart->exclude = newHash(7); cart->userInfo = emptyCartDb(); cart->sessionInfo = emptyCartDb(); return cart; } struct cart *cartOfNothing() /* Create a new, empty, cart with no real connection to the database. */ { return cartFromHash(newHash(0)); } struct cart *cartFromCgiOnly(char *userId, char *sessionId, char **exclude, struct hash *oldVars) /* Create a new cart that contains only CGI variables, nothing from the * database. */ { struct cart *cart = cartOfNothing(); cart->userId = userId; cart->sessionId = sessionId; loadCgiOverHash(cart, oldVars); if (exclude != NULL) { char *ex; while ((ex = *exclude++)) cartExclude(cart, ex); } return cart; } static void doDisconnectHub(struct cart *cart) { char *id = cartOptionalString(cart, "hubId"); if (id != NULL) { char buffer[1024]; safef(buffer, sizeof buffer, "hgHubConnect.hub.%s", id); cartRemove(cart, buffer); } cartRemove(cart, "hubId"); cartRemove(cart, hgHubDoDisconnect); } static void hideIfNotInCart(struct cart *cart, char *track) /* If this track is not mentioned in the cart, set it to hide */ { if (cartOptionalString(cart, track) == NULL) cartSetString(cart, track, "hide"); } void cartHideDefaultTracks(struct cart *cart) /* Hide all the tracks who have default visibilities in trackDb * that are something other than hide. Do this only if the * variable CART_HAS_DEFAULT_VISIBILITY is set in the cart. */ { char *defaultString = cartOptionalString(cart, CART_HAS_DEFAULT_VISIBILITY); boolean cartHasDefaults = (defaultString != NULL) && sameString(defaultString, "on"); if (!cartHasDefaults) return; char *db = cartString(cart, "db"); struct trackDb *tdb = hTrackDb(db); for(; tdb; tdb = tdb->next) { struct trackDb *parent = tdb->parent; if (parent && parent->isShow) hideIfNotInCart(cart, parent->track); if (tdb->visibility != tvHide) hideIfNotInCart(cart, tdb->track); } // Don't do this again until someone sets this variable, // presumably on session load. cartRemove(cart, CART_HAS_DEFAULT_VISIBILITY); } struct cart *cartNew(char *userId, char *sessionId, char **exclude, struct hash *oldVars) /* Load up cart from user & session id's. Exclude is a null-terminated list of * strings to not include */ { cgiApoptosisSetup(); if (cfgOptionBooleanDefault("showEarlyErrors", FALSE)) errAbortSetDoContentType(TRUE); if (cfgOptionBooleanDefault("suppressVeryEarlyErrors", FALSE)) htmlSuppressErrors(); struct cart *cart; struct sqlConnection *conn = cartDefaultConnector(); char *ex; boolean userIdFound = FALSE, sessionIdFound = FALSE; AllocVar(cart); cart->hash = newHash(12); cart->exclude = newHash(7); cart->userId = userId; cart->sessionId = sessionId; cart->userInfo = loadDb(conn, userDbTable(), userId, &userIdFound); cart->sessionInfo = loadDb(conn, sessionDbTable(), sessionId, &sessionIdFound); if (sessionIdFound) cartParseOverHash(cart, cart->sessionInfo->contents); else if (userIdFound) cartParseOverHash(cart, cart->userInfo->contents); char when[1024]; safef(when, sizeof(when), "open %s %s", userId, sessionId); cartTrace(cart, when, conn); loadCgiOverHash(cart, oldVars); // I think this is the place to justify old and new values cartJustify(cart, oldVars); #ifndef GBROWSE /* If some CGI other than hgSession been passed hgSession loading instructions, * apply those to cart before we do anything else. (If this is hgSession, * let it handle the settings so it can display feedback to the user.) */ boolean didSessionLoad = FALSE; if (! (cgiScriptName() && endsWith(cgiScriptName(), "hgSession"))) { if (cartVarExists(cart, hgsDoOtherUser)) { setUdcCacheDir(); char *otherUser = cartString(cart, hgsOtherUserName); char *sessionName = cartString(cart, hgsOtherUserSessionName); struct sqlConnection *conn2 = hConnectCentral(); cartLoadUserSession(conn2, otherUser, sessionName, cart, oldVars, hgsDoOtherUser); hDisconnectCentral(&conn2); cartTrace(cart, "after cartLUS", conn); didSessionLoad = TRUE; } else if (cartVarExists(cart, hgsDoLoadUrl)) { setUdcCacheDir(); char *url = cartString(cart, hgsLoadUrlName); struct lineFile *lf = netLineFileOpen(url); cartLoadSettings(lf, cart, oldVars, hgsDoLoadUrl); lineFileClose(&lf); cartTrace(cart, "after cartLS", conn); didSessionLoad = TRUE; } } #endif /* GBROWSE */ /* wire up the assembly hubs so we can operate without sql */ setUdcTimeout(cart); if (cartVarExists(cart, hgHubDoDisconnect)) doDisconnectHub(cart); pushWarnHandler(cartHubWarn); char *newDatabase = hubConnectLoadHubs(cart); popWarnHandler(); #ifndef GBROWSE if (didSessionLoad) cartCopyCustomTracks(cart); #endif /* GBROWSE */ if (newDatabase != NULL) { // this is some magic to use the defaultPosition and reset cart variables if (oldVars) { struct hashEl *hel; if ((hel = hashLookup(oldVars,"db")) != NULL) hel->val = "none"; else hashAdd(oldVars, "db", "none"); } cartSetString(cart,"db", newDatabase); } if (exclude != NULL) { while ((ex = *exclude++)) cartExclude(cart, ex); } cartDefaultDisconnector(&conn); if (didSessionLoad) cartHideDefaultTracks(cart); return cart; } static void updateOne(struct sqlConnection *conn, char *table, struct cartDb *cdb, char *contents, int contentSize) /* Update cdb in database. */ { struct dyString *dy = newDyString(4096); sqlDyStringPrintf(dy, "UPDATE %s SET contents='", table); sqlDyAppendEscaped(dy, contents); sqlDyStringPrintf(dy, "',lastUse=now(),useCount=%d ", cdb->useCount+1); sqlDyStringPrintf(dy, " where id=%u", cdb->id); if (cartDbUseSessionKey()) sqlDyStringPrintf(dy, " and sessionKey='%s'", cdb->sessionKey); sqlUpdate(conn, dy->string); dyStringFree(&dy); } void cartEncodeState(struct cart *cart, struct dyString *dy) /* Add a CGI-encoded var=val&... string of all cart variables to dy. */ { struct hashEl *el, *elList = hashElListHash(cart->hash); boolean firstTime = TRUE; char *s = NULL; for (el = elList; el != NULL; el = el->next) { if (!hashLookup(cart->exclude, el->name)) { if (firstTime) firstTime = FALSE; else dyStringAppendC(dy, '&'); dyStringAppend(dy, el->name); dyStringAppendC(dy, '='); s = cgiEncode(el->val); dyStringAppend(dy, s); freez(&s); } } hashElFreeList(&elList); } static void saveState(struct cart *cart) /* Save out state to permanent storage in both user and session db. */ { struct sqlConnection *conn = cartDefaultConnector(); struct dyString *encoded = newDyString(4096); /* Make up encoded string holding all variables. */ cartEncodeState(cart, encoded); /* Make up update statement unless it looks like a robot with * a great bunch of variables. */ if (encoded->stringSize < 16*1024 || cart->userInfo->useCount > 0) { updateOne(conn, userDbTable(), cart->userInfo, encoded->string, encoded->stringSize); updateOne(conn, sessionDbTable(), cart->sessionInfo, encoded->string, encoded->stringSize); } else { fprintf(stderr, "Cart stuffing bot? Not writing %d bytes to cart on first use of %d from IP=%s\n", encoded->stringSize, cart->userInfo->id, cgiRemoteAddr()); /* Do increment the useCount so that cookie-users don't get stuck here: */ updateOne(conn, userDbTable(), cart->userInfo, "", 0); } /* Cleanup */ cartDefaultDisconnector(&conn); dyStringFree(&encoded); } void cartCheckout(struct cart **pCart) /* Free up cart and save it to database. */ { struct cart *cart = *pCart; if (cart != NULL) { saveState(cart); struct sqlConnection *conn = cartDefaultConnector(); cartTrace(cart, "checkout", conn); cartDefaultDisconnector(&conn); cartDbFree(&cart->userInfo); cartDbFree(&cart->sessionInfo); freeHash(&cart->hash); freeHash(&cart->exclude); freez(pCart); } } char *cartSessionVarName() /* Return name of CGI session ID variable. */ { return sessionVar; } char *cartSessionId(struct cart *cart) /* Return session id. */ { static char buf[256]; cartDbSecureId(buf, sizeof buf, cart->sessionInfo); return buf; } char *cartSidUrlString(struct cart *cart) /* Return session id string as in hgsid=N . */ { static char buf[64]; safef(buf, sizeof(buf), "%s=%s", cartSessionVarName(), cartSessionId(cart)); return buf; } char *cartUserId(struct cart *cart) /* Return session id. */ { static char buf[256]; cartDbSecureId(buf, sizeof buf, cart->userInfo); return buf; } static char *cartMultShadowVar(struct cart *cart, char *var) /* Return a pointer to the list variable shadow variable name for var. * Don't modify or free result. */ { static char multShadowVar[PATH_LEN]; safef(multShadowVar, sizeof(multShadowVar), "%s%s", cgiMultListShadowPrefix(), var); return multShadowVar; } static int cartRemoveAndCountNoShadow(struct cart *cart, char *var) /* Remove variable from cart, returning count of removed vars. */ { int removed = 0; struct hashEl *hel = hashLookup(cart->hash, var); while (hel != NULL) { struct hashEl *nextHel = hashLookupNext(hel); freez(&hel->val); hashRemove(cart->hash, var); removed++; hel = nextHel; } return removed; } static int cartRemoveAndCount(struct cart *cart, char *var) /* Remove variable from cart, returning count of removed vars. */ { int removed = cartRemoveAndCountNoShadow(cart, var); (void)cartRemoveAndCountNoShadow(cart, cartMultShadowVar(cart, var)); return removed; } void cartRemove(struct cart *cart, char *var) /* Remove variable from cart. */ { (void)cartRemoveAndCount(cart, var); } void cartRemoveExcept(struct cart *cart, char **except) /* Remove variables except those in null terminated except array * from cart. Except array may be NULL in which case all * are removed. */ { struct hash *exceptHash = newHash(10); struct hashEl *list = NULL, *el; char *s; /* Build up hash of things to exclude. */ if (except != NULL) { while ((s = *except++) != NULL) hashAdd(exceptHash, s, NULL); } /* Get all cart variables and remove most of them. */ list = hashElListHash(cart->hash); for (el = list; el != NULL; el = el->next) { if (!hashLookup(exceptHash, el->name)) cartRemove(cart, el->name); } /* Clean up. */ hashFree(&exceptHash); hashElFreeList(&list); } struct slPair *cartVarsLike(struct cart *cart, char *wildCard) /* Return a slPair list of cart vars that match the wildcard */ { struct slPair *cartVars = NULL; struct hashEl *el, *elList = hashElListHash(cart->hash); slSort(&elList, hashElCmp); for (el = elList; el != NULL; el = el->next) { if (wildMatch(wildCard, el->name)) slAddHead(&cartVars,slPairNew(el->name,el->val)); } hashElFreeList(&elList); return cartVars; } struct slPair *cartVarsWithPrefix(struct cart *cart, char *prefix) /* Return a slPair list of cart vars that begin with prefix */ { struct slPair *cartVars = NULL; struct hashEl *el, *elList = hashElListHash(cart->hash); slSort(&elList, hashElCmp); for (el = elList; el != NULL; el = el->next) { if (startsWith(prefix, el->name)) slAddHead(&cartVars,slPairNew(el->name,el->val)); } hashElFreeList(&elList); return cartVars; } struct slPair *cartVarsWithPrefixLm(struct cart *cart, char *prefix, struct lm *lm) /* Return list of cart vars that begin with prefix allocated in local memory. * Quite a lot faster than cartVarsWithPrefix. */ { struct slPair *cartVars = NULL; struct hash *hash = cart->hash; int hashSize = hash->size; struct hashEl *hel; int i; for (i=0; i<hashSize; ++i) { for (hel = hash->table[i]; hel != NULL; hel = hel->next) { if (startsWith(prefix, hel->name)) { struct slPair *pair; lmAllocVar(lm, pair); pair->name = lmCloneString(lm, hel->name); pair->val = lmCloneString(lm, hel->val); slAddHead(&cartVars, pair); } } } return cartVars; } void cartRemoveLike(struct cart *cart, char *wildCard) /* Remove all variable from cart that match wildCard. */ { struct slPair *cartVars = cartVarsLike(cart,wildCard); while (cartVars != NULL) { struct slPair *cartVar = slPopHead(&cartVars); cartRemove(cart, cartVar->name); freeMem(cartVar); } } void cartRemovePrefix(struct cart *cart, char *prefix) /* Remove variables with given prefix from cart. */ { struct slPair *cartVars = cartVarsWithPrefix(cart,prefix); while (cartVars != NULL) { struct slPair *cartVar = slPopHead(&cartVars); cartRemove(cart, cartVar->name); freeMem(cartVar); } } boolean cartVarExists(struct cart *cart, char *var) /* Return TRUE if variable is in cart. */ { return hashFindVal(cart->hash, var) != NULL; } boolean cartListVarExists(struct cart *cart, char *var) /* Return TRUE if a list variable is in cart (list may still be empty). */ { return cartVarExists(cart, cartMultShadowVar(cart, var)); } boolean cartListVarExistsAnyLevel(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix) /* Return TRUE if a list variable for tdb->track (or tdb->parent->track, * or tdb->parent->parent->track, etc.) is in cart (list itself may be NULL). */ { if (parentLevel) tdb = tdb->parent; for ( ; tdb != NULL; tdb = tdb->parent) { char var[PATH_LEN]; if (suffix[0] == '.' || suffix[0] == '_') safef(var, sizeof var, "%s%s%s", cgiMultListShadowPrefix(), tdb->track, suffix); else safef(var, sizeof var, "%s%s.%s", cgiMultListShadowPrefix(), tdb->track, suffix); char *cartSetting = hashFindVal(cart->hash, var); if (cartSetting != NULL) return TRUE; } return FALSE; } char *cartString(struct cart *cart, char *var) /* Return string valued cart variable. */ { return hashMustFindVal(cart->hash, var); } char *cartOptionalString(struct cart *cart, char *var) /* Return string valued cart variable or NULL if it doesn't exist. */ { return hashFindVal(cart->hash, var); } char *cartNonemptyString(struct cart *cart, char *name) /* Return string value associated with name. Return NULL * if value doesn't exist or if it is pure white space. */ { char *val = trimSpaces(cartOptionalString(cart, name)); if (val != NULL && val[0] == 0) val = NULL; return val; } char *cartUsualString(struct cart *cart, char *var, char *usual) /* Return variable value if it exists or usual if not. */ { char *s = cartOptionalString(cart, var); if (s == NULL) return usual; return s; } char *cartCgiUsualString(struct cart *cart, char *var, char *usual) /* Look for var in CGI, then in cart, if not found then return usual. */ { char *val = cgiOptionalString(var); if (val != NULL) return(val); if (cart != NULL) return cartUsualString(cart, var, usual); return(usual); } struct slName *cartOptionalSlNameList(struct cart *cart, char *var) /* Return slName list (possibly with multiple values for the same var) or * NULL if not found. */ { struct slName *slnList = NULL; struct hashEl *hel = hashLookup(cart->hash, var); while (hel != NULL) { if (hel->val != NULL) { struct slName *sln = slNameNew(hel->val); slAddHead(&slnList, sln); } hel = hashLookupNext(hel); } return slnList; } void cartAddString(struct cart *cart, char *var, char *val) /* Add string valued cart variable (if called multiple times on same var, * will create a list -- retrieve with cartOptionalSlNameList. */ { hashAdd(cart->hash, var, cloneString(val)); } void cartSetString(struct cart *cart, char *var, char *val) /* Set string valued cart variable. */ { hashUpdateDynamicVal(cart->hash, var, cloneString(val)); } int cartInt(struct cart *cart, char *var) /* Return int valued variable. */ { char *s = cartString(cart, var); return atoi(s); } int cartIntExp(struct cart *cart, char *var) /* Return integer valued expression in variable. */ { return intExp(cartString(cart, var)); } int cartUsualInt(struct cart *cart, char *var, int usual) /* Return variable value if it exists or usual if not. */ { char *s = cartOptionalString(cart, var); if (s == NULL) return usual; return atoi(s); } int cartUsualIntClipped(struct cart *cart, char *var, int usual, int minVal, int maxVal) /* Return integer variable clipped to lie between minVal/maxVal */ { int val = cartUsualInt(cart, var, usual); if (val < minVal) val = minVal; if (val > maxVal) val = maxVal; return val; } int cartCgiUsualInt(struct cart *cart, char *var, int usual) /* Look for var in CGI, then in cart, if not found then return usual. */ { char *val = cgiOptionalString(var); if (val != NULL) return atoi(val); if (cart != NULL) return cartUsualInt(cart, var, usual); return(usual); } void cartSetInt(struct cart *cart, char *var, int val) /* Set integer value. */ { char buf[32]; safef(buf, sizeof(buf), "%d", val); cartSetString(cart, var, buf); } double cartDouble(struct cart *cart, char *var) /* Return double valued variable. */ { char *s = cartString(cart, var); return atof(s); } double cartUsualDouble(struct cart *cart, char *var, double usual) /* Return variable value if it exists or usual if not. */ { char *s = cartOptionalString(cart, var); if (s == NULL) return usual; return atof(s); } double cartCgiUsualDouble(struct cart *cart, char *var, double usual) /* Look for var in CGI, then in cart, if not found then return usual. */ { char *val = cgiOptionalString(var); if (val != NULL) return atof(val); if (cart != NULL) return cartUsualDouble(cart, var, usual); return(usual); } void cartSetDouble(struct cart *cart, char *var, double val) /* Set double value. */ { char buf[32]; safef(buf, sizeof(buf), "%f", val); cartSetString(cart, var, buf); } boolean cartBoolean(struct cart *cart, char *var) /* Retrieve cart boolean. */ { char *s = cartString(cart, var); if (sameWord(s, "on") || atoi(s) > 0) return TRUE; else return FALSE; } boolean cartUsualBoolean(struct cart *cart, char *var, boolean usual) /* Return variable value if it exists or usual if not. */ { char *s = cartOptionalString(cart, var); if (s == NULL) return usual; return (sameWord(s, "on") || atoi(s) > 0); } boolean cartCgiUsualBoolean(struct cart *cart, char *var, boolean usual) /* Look for var in CGI, then in cart, if not found then return usual. */ { if (cgiBooleanDefined(var)) return cgiBoolean(var); if (cart != NULL) return cgiBoolean(var) || cartUsualBoolean(cart, var, usual); return(usual); } void cartSetBoolean(struct cart *cart, char *var, boolean val) /* Set boolean value. */ { // Be explicit because some cartBools overloaded with negative "disabled" values cartSetInt(cart,var,(val?1:0)); } void cartMakeTextVar(struct cart *cart, char *var, char *defaultVal, int charSize) /* Make a text control filled with value from cart if it exists or * default value otherwise. If charSize is zero it's calculated to fit * current value. Default value may be NULL. */ { cgiMakeTextVar(var, cartUsualString(cart, var, defaultVal), charSize); } void cartMakeIntVar(struct cart *cart, char *var, int defaultVal, int maxDigits) /* Make a text control filled with integer value - from cart if available * otherwise default. */ { cgiMakeIntVar(var, cartUsualInt(cart, var, defaultVal), maxDigits); } void cartMakeDoubleVar(struct cart *cart, char *var, double defaultVal, int maxDigits) /* Make a text control filled with integer value - from cart if available * otherwise default. */ { cgiMakeDoubleVar(var, cartUsualDouble(cart, var, defaultVal), maxDigits); } void cartMakeCheckBox(struct cart *cart, char *var, boolean defaultVal) /* Make a check box filled with value from cart if it exists or * default value otherwise. */ { cgiMakeCheckBox(var, cartUsualBoolean(cart, var, defaultVal)); } void cartMakeRadioButton(struct cart *cart, char *var, char *val, char *defaultVal) /* Make a radio button that is selected if cart variable exists and matches * value (or value matches default val if cart var doesn't exist). */ { boolean matches = sameString(val, cartUsualString(cart, var, defaultVal)); cgiMakeRadioButton(var, val, matches); } void cartSaveSession(struct cart *cart) /* Save session in a hidden variable. This needs to be called * somewhere inside of form or bad things will happen when user * has multiple windows open. */ { cgiMakeHiddenVar(sessionVar, cartSessionId(cart)); } static void cartDumpItem(struct hashEl *hel,boolean asTable) /* Dump one item in cart hash */ { char *var = htmlEncode(hel->name); char *val = htmlEncode((char *)(hel->val)); if (asTable) { printf("<TR><TD>%s</TD><TD>", var); int width=(strlen(val)+1)*8; if (width<100) width = 100; cgiMakeTextVarWithExtraHtml(hel->name, val, width, "onchange='setCartVar(this.name,this.value);'"); printf("</TD></TR>\n"); } else printf("%s %s\n", var, val); freeMem(var); freeMem(val); } void cartDumpList(struct hashEl *elList,boolean asTable) /* Dump list of cart variables optionally as a table with ajax update support. */ { struct hashEl *el; if (elList == NULL) return; slSort(&elList, hashElCmp); if (asTable) printf("<table>\n"); for (el = elList; el != NULL; el = el->next) cartDumpItem(el,asTable); if (asTable) { printf("<tr><td colspan=2> <em>count: %d</em></td></tr>\n",slCount(elList)); printf("</table>\n"); } hashElFreeList(&elList); } void cartDump(struct cart *cart) /* Dump contents of cart. */ { struct hashEl *elList = hashElListHash(cart->hash); cartDumpList(elList,cartVarExists(cart,CART_DUMP_AS_TABLE)); } void cartDumpPrefix(struct cart *cart, char *prefix) /* Dump all cart variables with prefix */ { struct hashEl *elList = cartFindPrefix(cart, prefix); cartDumpList(elList,cartVarExists(cart,CART_DUMP_AS_TABLE)); } void cartDumpLike(struct cart *cart, char *wildcard) /* Dump all cart variables matching wildcard */ { struct hashEl *elList = cartFindLike(cart, wildcard); cartDumpList(elList,cartVarExists(cart,CART_DUMP_AS_TABLE)); } char *cartFindFirstLike(struct cart *cart, char *wildCard) /* Find name of first variable that matches wildCard in cart. * Return NULL if none. */ { struct hashEl *el, *elList = hashElListHash(cart->hash); char *name = NULL; for (el = elList; el != NULL; el = el->next) { if (wildMatch(wildCard, el->name)) { name = el->name; break; } } hashElFreeList(&el); return name; } static struct hashEl *cartFindSome(struct cart *cart, char *pattern, boolean (*match)(const char *a, const char *b)) /* Return list of name/val pairs from cart where name matches * pattern. Free when done with hashElFreeList. */ { struct hashEl *el, *next, *elList = hashElListHash(cart->hash); struct hashEl *outList = NULL; for (el = elList; el != NULL; el = next) { next = el->next; if (match(pattern, el->name)) { slAddHead(&outList, el); } else { hashElFree(&el); } } return outList; } struct hashEl *cartFindLike(struct cart *cart, char *wildCard) /* Return list of name/val pairs from cart where name matches * wildcard. Free when done with hashElFreeList. */ { return cartFindSome(cart, wildCard, wildMatch); } struct hashEl *cartFindPrefix(struct cart *cart, char *prefix) /* Return list of name/val pairs from cart where name starts with * prefix. Free when done with hashElFreeList. */ { return cartFindSome(cart, prefix, startsWith); } static char *cookieDate() /* Return date string for cookie format. We'll have to * revisit this in 35 years.... */ { return "Thu, 31-Dec-2037 23:59:59 GMT"; } static char *getCookieId(char *cookieName) /* Get id value from cookie. */ { return findCookieData(cookieName); } static char *getSessionId() /* Get session id if any from CGI. */ { return cgiOptionalString("hgsid"); } static void clearDbContents(struct sqlConnection *conn, char *table, char * secureId) /* Clear out contents field of row in table that matches id. */ { if (!secureId) return; struct dyString *query = dyStringNew(256); char *sessionKey = NULL; unsigned int id = cartDbParseId(secureId, &sessionKey); sqlDyStringPrintf(query, "update %s set contents='' where id=%u", table, id); if (cartDbUseSessionKey()) { if (!sessionKey) sessionKey = ""; sqlDyStringPrintf(query, " and sessionKey='%s'", sessionKey); } sqlUpdate(conn, query->string); dyStringFree(&query); } void cartResetInDb(char *cookieName) /* Clear cart in database. */ { char *hguid = getCookieId(cookieName); char *hgsid = getSessionId(); struct sqlConnection *conn = cartDefaultConnector(); clearDbContents(conn, userDbTable(), hguid); clearDbContents(conn, sessionDbTable(), hgsid); cartDefaultDisconnector(&conn); } void cartWriteCookie(struct cart *cart, char *cookieName) /* Write out HTTP Set-Cookie statement for cart. */ { char *domain = cfgVal("central.domain"); if (sameWord("HTTPHOST", domain)) { // IE9 does not accept portnames in cookie domains char *hostWithPort = hHttpHost(); struct netParsedUrl npu; netParseUrl(hostWithPort, &npu); if (strchr(npu.host, '.') != NULL) // Domains without a . don't seem to be kept domain = cloneString(npu.host); else domain = NULL; } char userIdKey[256]; cartDbSecureId(userIdKey, sizeof userIdKey, cart->userInfo); // Some users reported blank cookie values. Do we see that here? if (sameString(userIdKey,"")) // make sure we do not write any blank cookies. { // Be sure we do not lose this message. // Because the error happens so early we cannot trust that the warn and error handlers // are setup correctly and working. verbose(1, "unexpected error in cartWriteCookie: userId string is empty."); dumpStack( "unexpected error in cartWriteCookie: userId string is empty."); warn( "unexpected error in cartWriteCookie: userId string is empty."); } else { if (!isEmpty(domain)) printf("Set-Cookie: %s=%s; path=/; domain=%s; expires=%s\r\n", cookieName, userIdKey, domain, cookieDate()); else printf("Set-Cookie: %s=%s; path=/; expires=%s\r\n", cookieName, userIdKey, cookieDate()); } if (geoMirrorEnabled()) { // This occurs after the user has manually choosen to go back to the original site; we store redirect value into a cookie so we // can use it in subsequent hgGateway requests before loading the user's cart char *redirect = cgiOptionalString("redirect"); if (redirect) { printf("Set-Cookie: redirect=%s; path=/; domain=%s; expires=%s\r\n", redirect, cgiServerName(), cookieDate()); } } } struct cart *cartForSession(char *cookieName, char **exclude, struct hash *oldVars) /* This gets the cart without writing any HTTP lines at all to stdout. */ { char *hguid = getCookieId(cookieName); char *hgsid = getSessionId(); struct cart *cart = cartNew(hguid, hgsid, exclude, oldVars); cartExclude(cart, sessionVar); if (sameOk(cfgOption("signalsHandler"), "on")) /* most cgis call this routine */ initSigHandlers(hDumpStackEnabled()); char *httpProxy = cfgOption("httpProxy"); /* most cgis call this routine */ if (httpProxy) setenv("http_proxy", httpProxy, TRUE); /* net.c cannot see the cart, pass the value through env var */ return cart; } struct cart *cartAndCookieWithHtml(char *cookieName, char **exclude, struct hash *oldVars, boolean doContentType) /* Load cart from cookie and session cgi variable. Write cookie * and optionally content-type part HTTP preamble to web page. Don't * write any HTML though. */ { if (doContentType) htmlPushEarlyHandlers(); else pushWarnHandler(cartEarlyWarningHandler); struct cart *cart = cartForSession(cookieName, exclude, oldVars); popWarnHandler(); cartWriteCookie(cart, cookieName); if (doContentType) { puts("Content-Type:text/html"); puts("\n"); } return cart; } struct cart *cartAndCookie(char *cookieName, char **exclude, struct hash *oldVars) /* Load cart from cookie and session cgi variable. Write cookie and * content-type part HTTP preamble to web page. Don't write any HTML though. */ { return cartAndCookieWithHtml(cookieName, exclude, oldVars, TRUE); } struct cart *cartAndCookieNoContent(char *cookieName, char **exclude, struct hash *oldVars) /* Load cart from cookie and session cgi variable. Don't write out * content type or any HTML. */ { return cartAndCookieWithHtml(cookieName, exclude, oldVars, FALSE); } static void cartErrorCatcher(void (*doMiddle)(struct cart *cart), struct cart *cart) /* Wrap error catcher around call to do middle. */ { int status = setjmp(htmlRecover); pushAbortHandler(htmlAbort); hDumpStackPushAbortHandler(); if (status == 0) doMiddle(cart); hDumpStackPopAbortHandler(); popAbortHandler(); } void cartEarlyWarningHandler(char *format, va_list args) /* Write an error message so user can see it before page is really started. */ { static boolean initted = FALSE; va_list argscp; va_copy(argscp, args); if (!initted) { htmStart(stdout, "Early Error"); initted = TRUE; } printf("%s", htmlWarnStartPattern()); htmlVaParagraph(format,args); printf("%s", htmlWarnEndPattern()); /* write warning/error message to stderr so they get logged. */ logCgiToStderr(); vfprintf(stderr, format, argscp); va_end(argscp); putc('\n', stderr); fflush(stderr); } void cartWarnCatcher(void (*doMiddle)(struct cart *cart), struct cart *cart, WarnHandler warner) /* Wrap error and warning handlers around doMiddle. */ { pushWarnHandler(warner); cartErrorCatcher(doMiddle, cart); popWarnHandler(); } static boolean inWeb = FALSE; static boolean didCartHtmlStart = FALSE; void cartHtmlStart(char *title) /* Write HTML header and put in normal error handler. */ { pushWarnHandler(htmlVaWarn); htmStart(stdout, title); didCartHtmlStart = TRUE; } void cartVaWebStart(struct cart *cart, char *db, char *format, va_list args) /* Print out pretty wrapper around things when working * from cart. */ { pushWarnHandler(htmlVaWarn); webStartWrapper(cart, trackHubSkipHubName(db), format, args, FALSE, FALSE); inWeb = TRUE; } void cartWebStart(struct cart *cart, char *db, char *format, ...) /* Print out pretty wrapper around things when working * from cart. */ { va_list args; va_start(args, format); cartVaWebStart(cart, db, format, args); va_end(args); jsIncludeFile("jquery.js", NULL); jsIncludeFile("utils.js", NULL); jsIncludeFile("ajax.js", NULL); // WTF - variable outside of a form on almost every page we make below? // Tim put this in. Talking with him it sounds like some pages might actually // depend on it. Not removing it until we have a chance to test. Best fix // might be to add it to cartSaveSession, though this would then no longer be // well named, and not all things have 'db.' Arrr. Probably best to remove // and test a bunch. cgiMakeHiddenVar("db", db); } void cartWebEnd() /* Write out HTML footer and get rid or error handler. */ { webEnd(); popWarnHandler(); inWeb = FALSE; } void cartFooter(void) /* Write out HTML footer, possibly with googleAnalytics too */ { #ifndef GBROWSE googleAnalytics(); /* can't do this in htmlEnd */ #endif /* GBROWSE */ htmlEnd(); /* because it is in a higher library */ } void cartHtmlEnd() /* Write out HTML footer and get rid or error handler. */ { if (inWeb) webEnd(); /* this does googleAnalytics for a lot of CGIs */ else if (didCartHtmlStart) cartFooter(); else return; popWarnHandler(); } void setThemeFromCart(struct cart *cart) /* If 'theme' variable is set in cart: overwrite background with the one from * defined for this theme Also set the "styleTheme", with additional styles * that can overwrite the main style settings */ { // Get theme from cart and use it to get background file from config; // format is browser.theme.<name>=<stylesheet>[,<background>] char *cartTheme = cartOptionalString(cart, "theme"); // XXXX which setting should take precedence? Currently browser.theme does. char *styleFile = cfgOption("browser.style"); if(styleFile != NULL) { char buf[512]; safef(buf, sizeof(buf), "<link rel='stylesheet' href='%s' type='text/css'>", styleFile); char *copy = cloneString(buf); htmlSetStyleTheme(copy); // for htmshell.c, used by hgTracks webSetStyle(copy); // for web.c, used by hgc } if(isNotEmpty(cartTheme)) { char *themeKey = catTwoStrings("browser.theme.", cartTheme); styleFile = cfgOption(themeKey); freeMem(themeKey); if (isEmpty(styleFile)) return; char * link = webTimeStampedLinkToResourceOnFirstCall(styleFile, TRUE); // resource file link wrapped in html if (link != NULL) { htmlSetStyleTheme(link); // for htmshell.c, used by hgTracks webSetStyle(link); // for web.c, used by hgc } } } void cartHtmlShellWithHead(char *head, char *title, void (*doMiddle)(struct cart *cart), char *cookieName, char **exclude, struct hash *oldVars) /* Load cart from cookie and session cgi variable. Write web-page * preamble including head and title, call doMiddle with cart, and write end of web-page. * Exclude may be NULL. If it exists it's a comma-separated list of * variables that you don't want to save in the cart between * invocations of the cgi-script. */ { struct cart *cart; char *db, *org, *pos; char titlePlus[2048]; char extra[2048]; pushWarnHandler(cartEarlyWarningHandler); cart = cartAndCookie(cookieName, exclude, oldVars); getDbAndGenome(cart, &db, &org, oldVars); pos = cartGetPosition(cart, db, NULL); pos = addCommasToPos(db, stripCommas(pos)); if(pos != NULL && oldVars != NULL) { struct hashEl *oldpos = hashLookup(oldVars, positionCgiName); if(oldpos != NULL && differentString(pos,oldpos->val)) cartSetString(cart,"lastPosition",oldpos->val); } *extra = 0; if (pos == NULL && org != NULL) safef(titlePlus,sizeof(titlePlus), "%s%s - %s",trackHubSkipHubName(org), extra, title ); else if (pos != NULL && org == NULL) safef(titlePlus,sizeof(titlePlus), "%s - %s",pos, title ); else if (pos == NULL && org == NULL) safef(titlePlus,sizeof(titlePlus), "%s", title ); else safef(titlePlus,sizeof(titlePlus), "%s%s %s - %s",trackHubSkipHubName(org), extra,pos, title ); popWarnHandler(); setThemeFromCart(cart); htmStartWithHead(stdout, head, titlePlus); cartWarnCatcher(doMiddle, cart, htmlVaWarn); cartCheckout(&cart); cartFooter(); } static void cartEmptyShellMaybeContent(void (*doMiddle)(struct cart *cart), char *cookieName, char **exclude, struct hash *oldVars, boolean doContentType) /* Get cart and cookies and set up error handling. * If doContentType, print out Content-type:text/html * but don't start writing any html yet. * The doMiddleFunction has to call cartHtmlStart(title), and * cartHtmlEnd(), as well as writing the body of the HTML. * oldVars - those in cart that are overlayed by cgi-vars are * put in optional hash oldVars. */ { struct cart *cart = cartAndCookieWithHtml(cookieName, exclude, oldVars, doContentType); setThemeFromCart(cart); cartWarnCatcher(doMiddle, cart, cartEarlyWarningHandler); cartCheckout(&cart); } void cartEmptyShell(void (*doMiddle)(struct cart *cart), char *cookieName, char **exclude, struct hash *oldVars) /* Get cart and cookies and set up error handling, but don't start writing any * html yet. The doMiddleFunction has to call cartHtmlStart(title), and * cartHtmlEnd(), as well as writing the body of the HTML. * oldVars - those in cart that are overlayed by cgi-vars are * put in optional hash oldVars. */ { cartEmptyShellMaybeContent(doMiddle, cookieName, exclude, oldVars, TRUE); } void cartEmptyShellNoContent(void (*doMiddle)(struct cart *cart), char *cookieName, char **exclude, struct hash *oldVars) /* Get cart and cookies and set up error handling. * The doMiddle function must write the Content-Type header line. * oldVars - those in cart that are overlayed by cgi-vars are * put in optional hash oldVars. */ { cartEmptyShellMaybeContent(doMiddle, cookieName, exclude, oldVars, FALSE); } void cartHtmlShell(char *title, void (*doMiddle)(struct cart *cart), char *cookieName, char **exclude, struct hash *oldVars) /* Load cart from cookie and session cgi variable. Write web-page * preamble, call doMiddle with cart, and write end of web-page. * Exclude may be NULL. If it exists it's a comma-separated list of * variables that you don't want to save in the cart between * invocations of the cgi-script. */ { cartHtmlShellWithHead("", title, doMiddle, cookieName, exclude, oldVars); } void cartSetDbConnector(DbConnector connector) /* Set the connector that will be used by the cart to connect to the * database. Default connector is hConnectCart */ { cartDefaultConnector = connector; } void cartSetDbDisconnector(DbDisconnect disconnector) /* Set the connector that will be used by the cart to disconnect from the * database. Default disconnector is hDisconnectCart */ { cartDefaultDisconnector = disconnector; } static void saveDefaultGsidLists(char *genomeDb, struct cart *cart) /* save the default lists of GSID subject and sequence IDs to 2 internal files under trash/ct for applications to use */ { char *outName = NULL; char *outName2= NULL; FILE *outF, *outF2; struct tempName tn; struct tempName tn2; struct sqlResult *sr=NULL, *sr2=NULL; char **row, **row2; char query[255], query2[255]; char *chp; struct sqlConnection *conn, *conn2; conn= hAllocConn(genomeDb); conn2= hAllocConn(genomeDb); trashDirFile(&tn, "ct", "gsidSubj", ".list"); outName = tn.forCgi; trashDirFile(&tn2, "ct", "gsidSeq", ".list"); outName2 = tn2.forCgi; outF = mustOpen(outName,"w"); outF2= mustOpen(outName2,"w"); sqlSafef(query, sizeof(query), "select distinct subjId from hgFixed.gsIdXref order by subjId"); sr = sqlGetResult(conn, query); while ((row = sqlNextRow(sr)) != NULL) { fprintf(outF, "%s\n", row[0]); sqlSafef(query2, sizeof(query2), "select dnaSeqId from hgFixed.gsIdXref where subjId='%s' order by dnaSeqId", row[0]); sr2 = sqlGetResult(conn2, query2); while ((row2 = sqlNextRow(sr2)) != NULL) { /* Remove "ss." from the front of the DNA sequence ID, so that they could be used both for DNA and protein MSA maf display */ chp = strstr(row2[0], "ss."); if (chp != NULL) { fprintf(outF2, "%s\t%s\n", chp+3L, row[0]); } else { fprintf(outF2, "%s\t%s\n", row2[0], row[0]); } } sqlFreeResult(&sr2); } sqlFreeResult(&sr); carefulClose(&outF); carefulClose(&outF2); hFreeConn(&conn); hFreeConn(&conn2); cartSetString(cart, gsidSubjList, outName); cartSetString(cart, gsidSeqList, outName2); } char *cartGetOrderFromFile(char *genomeDb, struct cart *cart, char *speciesUseFile) /* Look in a cart variable that holds the filename that has a list of * species to show in a maf file */ { char *val; struct dyString *orderDY = dyStringNew(256); char *words[16]; if ((val = cartUsualString(cart, speciesUseFile, NULL)) == NULL) { if (hIsGsidServer()) { saveDefaultGsidLists(genomeDb, cart); /* now it should be set */ val = cartUsualString(cart, speciesUseFile, NULL); if (val == NULL) errAbort("can't find species list file var '%s' in cart\n",speciesUseFile); } else { errAbort("can't find species list file var '%s' in cart\n",speciesUseFile); } } struct lineFile *lf = lineFileOpen(val, TRUE); if (lf == NULL) errAbort("can't open species list file %s",val); while( ( lineFileChopNext(lf, words, sizeof(words)/sizeof(char *)) )) dyStringPrintf(orderDY, "%s ",words[0]); return dyStringCannibalize(&orderDY); } char *cartGetOrderFromFileAndMsaTable(char *genomeDb, struct cart *cart, char *speciesUseFile, char *msaTable) /* This function is used for GSID server only. Look in a cart variable that holds the filename that has a list of * species to show in a maf file and also restrict the results by the IDs existing in an MSA table*/ { char *val; struct sqlResult *sr=NULL; char query[255]; struct sqlConnection *conn; struct dyString *orderDY = dyStringNew(256); char *words[16]; if ((val = cartUsualString(cart, speciesUseFile, NULL)) == NULL) { if (hIsGsidServer()) { saveDefaultGsidLists(genomeDb, cart); /* now it should be set */ val = cartUsualString(cart, speciesUseFile, NULL); if (val == NULL) errAbort("can't find species list file var '%s' in cart\n",speciesUseFile); } else { errAbort("can't find species list file var '%s' in cart\n",speciesUseFile); } } struct lineFile *lf = lineFileOpen(val, TRUE); if (lf == NULL) errAbort("can't open species list file %s",val); if (hIsGsidServer()) { conn= hAllocConn(genomeDb); while( ( lineFileChopNext(lf, words, sizeof(words)/sizeof(char *)) )) { sqlSafef(query, sizeof(query), "select id from %s where id like '%%%s'", msaTable, words[0]); sr = sqlGetResult(conn, query); if (sqlNextRow(sr) != NULL) { dyStringPrintf(orderDY, "%s ",words[0]); sqlFreeResult(&sr); } } hFreeConn(&conn); } else { while( ( lineFileChopNext(lf, words, sizeof(words)/sizeof(char *)) )) { dyStringPrintf(orderDY, "%s ",words[0]); } } return dyStringCannibalize(&orderDY); } char *cartLookUpVariableClosestToHome(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix,char **pVariable) /* Returns value or NULL for a cart variable from lowest level on up. Optionally * fills the non NULL pVariable with the actual name of the variable in the cart */ { if (parentLevel) tdb = tdb->parent; for ( ; tdb != NULL; tdb = tdb->parent) { char buf[512]; if (suffix[0] == '.' || suffix[0] == '_') safef(buf, sizeof buf, "%s%s", tdb->track,suffix); else safef(buf, sizeof buf, "%s.%s", tdb->track,suffix); char *cartSetting = hashFindVal(cart->hash, buf); if (cartSetting != NULL) { if(pVariable != NULL) *pVariable = cloneString(buf); return cartSetting; } } if (pVariable != NULL) *pVariable = NULL; return NULL; } void cartRemoveVariableClosestToHome(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix) /* Looks for then removes a cart variable from lowest level on up: subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */ { char *var = NULL; (void)cartLookUpVariableClosestToHome(cart,tdb,parentLevel,suffix,&var); if (var != NULL) { cartRemove(cart,var); freeMem(var); } } char *cartStringClosestToHome(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix) /* Returns value or Aborts for a cart string from lowest level on up: subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */ { char *setting = cartOptionalStringClosestToHome(cart,tdb,parentLevel,suffix); if (setting == NULL) errAbort("cartStringClosestToHome: '%s' not found", suffix); return setting; } boolean cartVarExistsAnyLevel(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix) /* Returns TRUE if variable exists anywhere, looking from lowest level on up: subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */ { return (NULL != cartOptionalStringClosestToHome(cart,tdb,parentLevel,suffix)); } char *cartUsualStringClosestToHome(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix, char *usual) /* Returns value or {usual} for a cart string from lowest level on up: subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */ { char *setting = cartOptionalStringClosestToHome(cart,tdb,parentLevel,suffix); if(setting == NULL) setting = usual; return setting; } boolean cartBooleanClosestToHome(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix) /* Returns value or Aborts for a cart boolean ('on' or != 0) from lowest level on up: subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */ { char *setting = cartStringClosestToHome(cart,tdb,parentLevel,suffix); return (sameString(setting, "on") || atoi(setting) > 0); } boolean cartUsualBooleanClosestToHome(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix,boolean usual) /* Returns value or {usual} for a cart boolean ('on' or != 0) from lowest level on up: subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */ { char *setting = cartOptionalStringClosestToHome(cart,tdb,parentLevel,suffix); if (setting == NULL) return usual; return (sameString(setting, "on") || atoi(setting) > 0); } int cartUsualIntClosestToHome(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix, int usual) /* Returns value or {usual} for a cart int from lowest level on up: subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */ { char *setting = cartOptionalStringClosestToHome(cart,tdb,parentLevel,suffix); if (setting == NULL) return usual; return atoi(setting); } double cartUsualDoubleClosestToHome(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix, double usual) /* Returns value or {usual} for a cart fp double from lowest level on up: subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */ { char *setting = cartOptionalStringClosestToHome(cart,tdb,parentLevel,suffix); if (setting == NULL) return usual; return atof(setting); } struct slName *cartOptionalSlNameListClosestToHome(struct cart *cart, struct trackDb *tdb, boolean parentLevel, char *suffix) /* Return slName list (possibly with multiple values for the same var) from lowest level on up: subtrackName.suffix, then compositeName.view.suffix, then compositeName.suffix */ { char *var = NULL; cartLookUpVariableClosestToHome(cart,tdb,parentLevel,suffix,&var); if (var == NULL) return NULL; struct slName *slNames = cartOptionalSlNameList(cart,var); freeMem(var); return slNames; } void cartRemoveAllForTdb(struct cart *cart, struct trackDb *tdb) /* Remove all variables from cart that are associated with this tdb. */ { char setting[256]; safef(setting,sizeof(setting),"%s.",tdb->track); cartRemovePrefix(cart,setting); safef(setting,sizeof(setting),"%s_",tdb->track); // TODO: All should be {track}.{varName}... Fix {track}_sel cartRemovePrefix(cart,setting); cartRemove(cart,tdb->track); } void cartRemoveAllForTdbAndChildren(struct cart *cart, struct trackDb *tdb) /* Remove all variables from cart that are associated with this tdb and it's children. */ { struct trackDb *subTdb; for (subTdb=tdb->subtracks;subTdb!=NULL;subTdb=subTdb->next) cartRemoveAllForTdbAndChildren(cart,subTdb); cartRemoveAllForTdb(cart,tdb); saveState(cart); } char *cartOrTdbString(struct cart *cart, struct trackDb *tdb, char *var, char *defaultVal) /* Look first in cart, then in trackDb for var. Return defaultVal if not found. */ { char *tdbDefault = trackDbSettingClosestToHomeOrDefault(tdb, var, defaultVal); boolean parentLevel = isNameAtParentLevel(tdb, var); return cartUsualStringClosestToHome(cart, tdb, parentLevel, var, tdbDefault); } int cartOrTdbInt(struct cart *cart, struct trackDb *tdb, char *var, int defaultVal) /* Look first in cart, then in trackDb for var. Return defaultVal if not found. */ { char *a = cartOrTdbString(cart, tdb, var, NULL); if (a == NULL) return defaultVal; else return atoi(a); } double cartOrTdbDouble(struct cart *cart, struct trackDb *tdb, char *var, double defaultVal) /* Look first in cart, then in trackDb for var. Return defaultVal if not found. */ { char *a = cartOrTdbString(cart, tdb, var, NULL); if (a == NULL) return defaultVal; else return atof(a); } boolean cartOrTdbBoolean(struct cart *cart, struct trackDb *tdb, char *var, boolean defaultVal) /* Look first in cart, then in trackDb for var. Return defaultVal if not found. */ { boolean tdbVal = defaultVal; char *tdbSetting = trackDbSetting(tdb, var); if (tdbSetting != NULL) tdbVal = trackDbSettingClosestToHomeOn(tdb, var); return cartUsualBooleanClosestToHome(cart, tdb, isNameAtParentLevel(tdb, var), var, tdbVal); } // These macros allow toggling warn messages to NOOPS when no longer debugging //#define DEBUG_WITH_WARN #ifdef DEBUG_WITH_WARN #define WARN warn #define ASSERT assert #else///ifndef DEBUG_WITH_WARN #define WARN(...) #define ASSERT(...) #endif///ndef DEBUG_WITH_WARN boolean cartValueHasChanged(struct cart *newCart,struct hash *oldVars,char *setting, boolean ignoreRemoved,boolean ignoreCreated) /* Returns TRUE if new cart setting has changed from old cart setting */ { char *oldValue = hashFindVal(oldVars,setting); if (oldValue == NULL) return FALSE; // All vars changed by cgi will be found in old vars char *newValue = cartOptionalString(newCart,setting); if (newValue == NULL) return (!ignoreRemoved); if (sameString(oldValue,CART_VAR_EMPTY)) { if (sameString(newValue,"hide") || sameString(newValue,"off") || sameString(newValue,"0")) // Special cases DANGER! return FALSE; } return (differentString(newValue,oldValue)); } static int cartNamesPruneChanged(struct cart *newCart,struct hash *oldVars, struct slPair **cartNames,boolean ignoreRemoved,boolean unChanged) /* Prunes a list of cartNames if the settings have changed between new and old cart. Returns pruned count */ { int pruned = 0; struct slPair *oldList = *cartNames; struct slPair *newList = NULL; struct slPair *oneName = NULL; while ((oneName = slPopHead(&oldList)) != NULL) { boolean thisOneChanged = cartValueHasChanged(newCart,oldVars,oneName->name,ignoreRemoved,TRUE); if (unChanged != thisOneChanged) slAddHead(&newList,oneName); else { pruned++; } } *cartNames = newList; return pruned; } int cartRemoveFromTdbTree(struct cart *cart,struct trackDb *tdb,char *suffix,boolean skipParent) /* Removes a 'trackName.suffix' from all tdb descendents (but not parent). If suffix NULL then removes 'trackName' which holds visibility */ { int removed = 0; boolean vis = (suffix == NULL || *suffix == '\0'); struct slRef *tdbRef, *tdbRefList = trackDbListGetRefsToDescendants(skipParent?tdb->subtracks:tdb); for (tdbRef = tdbRefList; tdbRef != NULL; tdbRef = tdbRef->next) { struct trackDb *descendentTdb = tdbRef->val; char setting[512]; if (vis) safef(setting,sizeof(setting),"%s",descendentTdb->track); else safef(setting,sizeof(setting),"%s.%s",descendentTdb->track,suffix); removed += cartRemoveAndCount(cart,setting); } return removed; } static int cartRemoveOldFromTdbTree(struct cart *newCart,struct hash *oldVars,struct trackDb *tdb, char *suffix,char *parentVal,boolean skipParent) // Removes a 'trackName.suffix' from all tdb descendents (but not parent), BUT ONLY // IF OLD or same as parentVal. If suffix NULL then removes 'trackName' which holds visibility { int removed = 0; boolean vis = (suffix == NULL || *suffix == '\0'); struct slRef *tdbRef, *tdbRefList = trackDbListGetRefsToDescendants(skipParent?tdb->subtracks:tdb); for (tdbRef = tdbRefList; tdbRef != NULL; tdbRef = tdbRef->next) { struct trackDb *descendentTdb = tdbRef->val; char setting[512]; if (vis) safef(setting,sizeof(setting),"%s",descendentTdb->track); else safef(setting,sizeof(setting),"%s.%s",descendentTdb->track,suffix); char *newVal = cartOptionalString(newCart,setting); if ( newVal != NULL && ( (parentVal != NULL && sameString(newVal,parentVal)) || (FALSE == cartValueHasChanged(newCart,oldVars,setting,TRUE,FALSE)))) removed += cartRemoveAndCount(newCart,setting); } return removed; } static boolean cartTdbOverrideSuperTracks(struct cart *cart,struct trackDb *tdb, boolean ifJustSelected) // When when the child of a hidden supertrack is foudn and selected, then shape the // supertrack accordingly. Returns TRUE if any cart changes are made { // This is only pertinent to supertrack children just turned on if (!tdbIsSuperTrackChild(tdb)) return FALSE; char setting[512]; // Must be from having just selected the track in findTracks. // This will carry with it the "_sel" setting. if (ifJustSelected) { safef(setting,sizeof(setting),"%s_sel",tdb->track); if (!cartVarExists(cart,setting)) return FALSE; cartRemove(cart,setting); // Unlike composite subtracks, supertrack children keep } // the "_sel" setting only for detecting this moment // if parent is not hidden then nothing to do ASSERT(tdb->parent != NULL && tdbIsSuperTrack(tdb->parent)); enum trackVisibility vis = tdbVisLimitedByAncestry(cart, tdb->parent, FALSE); if (vis != tvHide) return FALSE; // Now turn all other supertrack children to hide and the supertrack to visible struct slRef *childRef; for (childRef = tdb->parent->children;childRef != NULL; childRef = childRef->next) { struct trackDb *child = childRef->val; if (child == tdb) continue; // Make sure this child hasn't also just been turned on! safef(setting,sizeof(setting),"%s_sel",child->track); if (cartVarExists(cart,setting)) { cartRemove(cart,setting); // Unlike composite subtracks, supertrack children keep continue; // the "_sel" setting only for detecting this moment } // hide this sibling if not already hidden char *cartVis = cartOptionalString(cart,child->track); if (cartVis != NULL) { if (child->visibility == tvHide) cartRemove(cart,child->track); else if (hTvFromString(cartVis) != tvHide) cartSetString(cart,child->track,"hide"); } else if (child->visibility != tvHide) cartSetString(cart,child->track,"hide"); } // and finally show the parent cartSetString(cart,tdb->parent->track,"show"); WARN("Set %s to 'show'",tdb->parent->track); return TRUE; } static int cartTdbParentShapeVis(struct cart *cart,struct trackDb *parent,char *view, struct hash *subVisHash,boolean reshapeFully) // This shapes one level of vis (view or container) based upon subtrack specific visibility. // Returns count of tracks affected { ASSERT(view || (tdbIsContainer(parent) && tdbIsContainerChild(parent->subtracks))); struct trackDb *subtrack = NULL; char setting[512]; safef(setting,sizeof(setting),"%s",parent->track); enum trackVisibility visMax = tvHide; enum trackVisibility visOrig = tdbVisLimitedByAncestry(cart, parent, FALSE); // Should walk through children to get max new vis for this parent for (subtrack = parent->subtracks;subtrack != NULL;subtrack = subtrack->next) { char *foundVis = hashFindVal(subVisHash, subtrack->track); // if the subtrack doesn't have if (foundVis != NULL) // individual vis AND... { enum trackVisibility visSub = hTvFromString(foundVis); if (tvCompare(visMax, visSub) >= 0) visMax = visSub; } else if (!reshapeFully && visOrig != tvHide) { int fourState = subtrackFourStateChecked(subtrack,cart); if (fourStateVisible(fourState)) // subtrack must be visible { enum trackVisibility visSub = tdbVisLimitedByAncestry(cart, subtrack, FALSE); if (tvCompare(visMax, visSub) >= 0) visMax = visSub; } } } // Now we need to update non-subtrack specific vis/sel in cart int countUnchecked=0; int countVisChanged=0; // If view, this should always be set, since if a single view needs // to be promoted, the composite will go to full. if (tdbIsCompositeView(parent)) { cartSetString(cart,setting,hStringFromTv(visMax)); // Set this explicitly. countVisChanged++; // The visOrig may be inherited! } if (visMax != visOrig || reshapeFully) { if (!tdbIsCompositeView(parent)) // view vis is always shaped, { // but composite vis is conditionally shaped. cartSetString(cart,setting,hStringFromTv(visMax)); // Set this explicitly. countVisChanged++; if (visOrig == tvHide && tdbIsSuperTrackChild(parent)) cartTdbOverrideSuperTracks(cart,parent,FALSE); // deal with superTrack vis! cleanup } // Now set all subtracks that inherit vis back to visOrig for (subtrack = parent->subtracks;subtrack != NULL;subtrack = subtrack->next) { int fourState = subtrackFourStateChecked(subtrack,cart); if (!fourStateChecked(fourState)) cartRemove(cart,subtrack->track); // Remove subtrack level vis if it isn't even checked else // subtrack is checked (should include subtrack level vis) { char *visFromHash = hashFindVal(subVisHash, subtrack->track); if (tdbIsMultiTrack(parent)) // MultiTrack vis is ALWAYS inherited vis cartRemove(cart,subtrack->track); // and non-selected should not have vis if (visFromHash == NULL) // if the subtrack doesn't have individual vis AND... { if (reshapeFully || visOrig == tvHide) { // uncheck subtrackFourStateCheckedSet(subtrack, cart,FALSE,fourStateEnabled(fourState)); cartRemove(cart,subtrack->track); // Remove it if it exists, just in case countUnchecked++; } else if (visOrig != tvHide) { cartSetString(cart,subtrack->track,hStringFromTv(visOrig)); countVisChanged++; } } else // This subtrack has explicit vis { enum trackVisibility vis = hTvFromString(visFromHash); if (vis == visMax) { cartRemove(cart,subtrack->track); // Remove vis which should now be inherited countVisChanged++; } } } } } else if (tdbIsMultiTrack(parent)) { // MultiTrack vis is ALWAYS inherited vis so remove any subtrack specific vis struct hashCookie brownie = hashFirst(subVisHash); struct hashEl* cartVar = NULL; while ((cartVar = hashNext(&brownie)) != NULL) { if (!endsWith(cartVar->name,"_sel")) cartRemove(cart,cartVar->name); } } if (countUnchecked + countVisChanged) { WARN("%s visOrig:%s visMax:%s unchecked:%d Vis changed:%d",parent->track, hStringFromTv(visOrig),hStringFromTv(visMax),countUnchecked,countVisChanged); } return (countUnchecked + countVisChanged); } boolean cartTdbTreeReshapeIfNeeded(struct cart *cart,struct trackDb *tdbContainer) /* When subtrack vis is set via findTracks, and composite has no cart settings, then "shape" composite to match found */ { if (!tdbIsContainer(tdbContainer)) return FALSE; // Don't do any shaping // First look for subtrack level vis char setting[512]; struct trackDb *subtrack = NULL; struct trackDb *tdbView = NULL; struct hash *subVisHash = newHash(0); struct slRef *tdbRef, *tdbRefList = trackDbListGetRefsToDescendantLeaves(tdbContainer->subtracks); for (tdbRef = tdbRefList; tdbRef != NULL; tdbRef = tdbRef->next) { subtrack = tdbRef->val; char *val=cartOptionalString(cart,subtrack->track); if (val && differentString(val,"hide")) // NOTE should we include hide? { int fourState = subtrackFourStateChecked(subtrack,cart); if (fourStateVisible(fourState)) // subtrack is checked too { WARN("Subtrack level vis %s=%s",subtrack->track,val); hashAdd(subVisHash,subtrack->track,val); safef(setting,sizeof(setting),"%s_sel",subtrack->track); hashAdd(subVisHash,setting,subtrack); // Add the "_sel" setting which should also exist. } // Point it to subtrack } } slFreeList(&tdbRefList); if (hashNumEntries(subVisHash) == 0) { //WARN("No subtrack level vis for %s",tdbContainer->track); return FALSE; } // Next look for any cart settings other than subtrack vis/sel // New directive means if composite is hidden, then ignore previous and don't bother checking cart. boolean reshapeFully = (tdbVisLimitedByAncestry(cart, tdbContainer, FALSE) == tvHide); boolean hasViews = tdbIsCompositeView(tdbContainer->subtracks); WARN("reshape: %s",reshapeFully?"Fully":"Incrementally"); // Now shape views and composite to match subtrack specific visibility int count = 0; if (hasViews) { for (tdbView = tdbContainer->subtracks;tdbView != NULL; tdbView = tdbView->next ) { char *view = NULL; if (tdbIsView(tdbView,&view) ) count += cartTdbParentShapeVis(cart,tdbView,view,subVisHash,reshapeFully); } if (count > 0) { // At least one view was shaped, so all views will get explicit vis. // This means composite must be set to full enum trackVisibility visOrig = tdbVisLimitedByAncestry(cart, tdbContainer, FALSE); cartSetString(cart,tdbContainer->track,"full"); // Now set composite to full. if (visOrig == tvHide && tdbIsSuperTrackChild(tdbContainer)) cartTdbOverrideSuperTracks(cart,tdbContainer,FALSE);// deal with superTrack vis cleanup } } else // If no views then composite is not set to fuul but to max of subtracks count = cartTdbParentShapeVis(cart,tdbContainer,NULL,subVisHash,reshapeFully); hashFree(&subVisHash); // If reshaped, be sure to set flag to stop composite cleanup if (count > 0) tdbExtrasReshapedCompositeSet(tdbContainer); return TRUE; } boolean cartTdbTreeCleanupOverrides(struct trackDb *tdb,struct cart *newCart,struct hash *oldVars, struct lm *lm) /* When container or composite/view settings changes, remove subtrack specific settings Returns TRUE if any cart vars are removed */ { boolean anythingChanged = cartTdbOverrideSuperTracks(newCart,tdb,TRUE); if (!tdbIsContainer(tdb)) return anythingChanged; // If composite has been reshaped then don't clean it up if (tdbExtrasReshapedComposite(tdb)) return anythingChanged; // vis is a special additive case! composite or view level changes then remove subtrack vis boolean containerVisChanged = cartValueHasChanged(newCart,oldVars,tdb->track,TRUE,FALSE); if (containerVisChanged) { // If just created and if vis is the same as tdb default then vis has not changed char *cartVis = cartOptionalString(newCart,tdb->track); char *oldValue = hashFindVal(oldVars,tdb->track); if (cartVis && oldValue == NULL && hTvFromString(cartVis) == tdb->visibility) containerVisChanged = FALSE; } struct trackDb *tdbView = NULL; struct slPair *oneName = NULL; char *suffix = NULL; int clensed = 0; // Build list of current settings for container or composite and views char setting[512]; safef(setting,sizeof(setting),"%s.",tdb->track); char * view = NULL; boolean hasViews = FALSE; struct slPair *changedSettings = cartVarsWithPrefixLm(newCart, setting, lm); for (tdbView = tdb->subtracks;tdbView != NULL; tdbView = tdbView->next) { if (!tdbIsView(tdbView,&view)) break; hasViews = TRUE; char *cartVis = cartOptionalString(newCart,tdbView->track); if (cartVis != NULL) // special to get viewVis in the list { lmAllocVar(lm, oneName); oneName->name = lmCloneString(lm, tdbView->track); oneName->val = lmCloneString(lm, cartVis); slAddHead(&changedSettings,oneName); } // Now the non-vis settings safef(setting,sizeof(setting),"%s.",tdbView->track); struct slPair *changeViewSettings = cartVarsWithPrefixLm(newCart, setting, lm); changedSettings = slCat(changedSettings, changeViewSettings); } if (changedSettings == NULL && !containerVisChanged) return anythingChanged; // Prune list to only those which have changed if (changedSettings != NULL) { (void)cartNamesPruneChanged(newCart,oldVars,&changedSettings,TRUE,FALSE); if (changedSettings == NULL && !containerVisChanged) return anythingChanged; } // Walk through views if (hasViews) { for (tdbView = tdb->subtracks;tdbView != NULL; tdbView = tdbView->next) { char *cartVis = NULL; boolean viewVisChanged = FALSE; if (!tdbIsView(tdbView,&view)) break; struct slPair *leftOvers = NULL; // Walk through settings that match this view while ((oneName = slPopHead(&changedSettings)) != NULL) { suffix = NULL; if (startsWith(tdbView->track,oneName->name)) { suffix = oneName->name + strlen(tdbView->track); if (*suffix == '.') // NOTE: standardize on '.' since its is so pervasive suffix++; else if (isalnum(*suffix)) // viewTrackName is subset of another track! suffix = NULL; // add back to list for next round } if (suffix == NULL) { slAddHead(&leftOvers,oneName); continue; } if (*suffix == '\0') { viewVisChanged = TRUE; cartVis = oneName->val; } else // be certain to exclude vis settings here if (cartRemoveOldFromTdbTree(newCart,oldVars,tdbView,suffix,oneName->val,TRUE) > 0) clensed++; //slPairFree(&oneName); // lm memory so free not needed } if (viewVisChanged) { // If just created and if vis is the same as tdb default then vis has not changed char *oldValue = hashFindVal(oldVars,tdbView->track); if (cartVis && oldValue == NULL && hTvFromString(cartVis) != tdbView->visibility) viewVisChanged = FALSE; } if (containerVisChanged || viewVisChanged) { // vis is a special additive case! WARN("Removing subtrack vis for %s.%s",tdb->track,view); char *viewVis = hStringFromTv(tdbVisLimitedByAncestry(newCart, tdbView, FALSE)); if (cartRemoveOldFromTdbTree(newCart,oldVars,tdbView,NULL,viewVis,TRUE) > 0) clensed++; } changedSettings = leftOvers; } } // Now deal with anything remaining at the container level while ((oneName = slPopHead(&changedSettings)) != NULL) { suffix = oneName->name + strlen(tdb->track) + 1; if (cartRemoveOldFromTdbTree(newCart,oldVars,tdb,suffix,oneName->val,TRUE) > 0) clensed++; } if (containerVisChanged && !hasViews) { // vis is a special additive case! char *vis = hStringFromTv(tdbVisLimitedByAncestry(newCart, tdb, FALSE)); if (cartRemoveOldFromTdbTree(newCart,oldVars,tdb,NULL,vis,TRUE) > 0) clensed++; } anythingChanged = (anythingChanged || (clensed > 0)); return anythingChanged; } void cgiExitTime(char *cgiName, long enteredMainTime) /* single stderr print out called at end of CGI binaries to record run * time in apache error_log */ { if (sameWord("yes", cfgOptionDefault("browser.cgiTime", "yes")) ) fprintf(stderr, "CGI_TIME: %s: Overall total time: %ld millis\n", cgiName, clock1000() - enteredMainTime); } void cartCheckForCustomTracks(struct cart *cart, struct dyString *dyMessage) /* Scan cart for ctfile_<db> variables. Tally up the databases that have * live custom tracks and those that have expired custom tracks. */ /* While we're at it, also look for saved blat results. */ { struct hashEl *helList = cartFindPrefix(cart, CT_FILE_VAR_PREFIX); if (helList != NULL) { struct hashEl *hel; boolean gotLiveCT = FALSE, gotExpiredCT = FALSE; struct slName *liveDbList = NULL, *expiredDbList = NULL, *sln = NULL; for (hel = helList; hel != NULL; hel = hel->next) { char *db = hel->name + strlen(CT_FILE_VAR_PREFIX); boolean thisGotLiveCT = FALSE, thisGotExpiredCT = FALSE; /* If the file doesn't exist, just remove the cart variable so it * doesn't get copied from session to session. If it does exist, * leave it up to customFactoryTestExistence to parse the file for * possible customTrash table references, some of which may exist * and some not. */ if (!fileExists(hel->val)) { cartRemove(cart, hel->name); thisGotExpiredCT = TRUE; } else { customFactoryTestExistence(db, hel->val, &thisGotLiveCT, &thisGotExpiredCT); } if (thisGotLiveCT) slNameAddHead(&liveDbList, db); if (thisGotExpiredCT) slNameAddHead(&expiredDbList, db); gotLiveCT |= thisGotLiveCT; gotExpiredCT |= thisGotExpiredCT; } if (gotLiveCT) { slSort(&liveDbList, slNameCmp); dyStringPrintf(dyMessage, "<P>Note: the session has at least one active custom " "track (in database "); for (sln = liveDbList; sln != NULL; sln = sln->next) dyStringPrintf(dyMessage, "<A HREF=\"hgCustom?%s&db=%s\">%s</A>%s", cartSidUrlString(cart), sln->name, sln->name, (sln->next ? sln->next->next ? ", " : " and " : "")); dyStringAppend(dyMessage, "; click on the database link " "to manage custom tracks). "); } if (gotExpiredCT) { slSort(&expiredDbList, slNameCmp); dyStringPrintf(dyMessage, "<P>Note: the session has at least one expired custom " "track (in database "); for (sln = expiredDbList; sln != NULL; sln = sln->next) dyStringPrintf(dyMessage, "%s%s", sln->name, (sln->next ? sln->next->next ? ", " : " and " : "")); dyStringPrintf(dyMessage, "), so it may not appear as originally intended. "); } dyStringPrintf(dyMessage, "These custom tracks should not expire, however, " "the UCSC Genome Browser is not a data storage service; " "<b>please keep a local backup of your sessions contents " "and custom track data</b>. </P>"); slNameFreeList(&liveDbList); slNameFreeList(&expiredDbList); } /* Check for saved blat results (quasi custom track). */ char *ss = cartOptionalString(cart, "ss"); if (isNotEmpty(ss)) { char buf[1024]; char *words[2]; int wordCount; boolean exists = FALSE; safecpy(buf, sizeof(buf), ss); wordCount = chopLine(buf, words); if (wordCount < 2) exists = FALSE; else exists = fileExists(words[0]) && fileExists(words[1]); if (exists) dyStringPrintf(dyMessage, "<P>Note: the session contains BLAT results. "); else dyStringPrintf(dyMessage, "<P>Note: the session contains an expired reference to " "previously saved BLAT results, so it may not appear as " "originally intended. "); dyStringPrintf(dyMessage, "BLAT results are subject to an " "<A HREF=\"../goldenPath/help/hgSessionHelp.html#CTs\" TARGET=_BLANK>" "expiration policy</A>."); } } char *cartGetPosition(struct cart *cart, char *database, struct cart **pLastDbPosCart) /* get the current position in cart as a string chr:start-end. * This can handle the special CGI params 'default' and 'lastDbPos' * Returned value has to be freed. Returns * default position of assembly is no position set in cart nor as CGI var. * Returns NULL if no position set anywhere and no default position. * For virtual modes, returns the type and extraState. */ { // position=lastDbPos in URL? -> go back to the last browsed position for this db char *position = NULL; char *defaultPosition = hDefaultPos(database); struct cart *lastDbPosCart = cartOfNothing(); boolean gotCart = FALSE; char dbPosKey[256]; safef(dbPosKey, sizeof(dbPosKey), "position.%s", database); if (sameOk(cgiOptionalString("position"), "lastDbPos")) { char *dbLocalPosContent = cartUsualString(cart, dbPosKey, NULL); if (dbLocalPosContent) { //warn("dbLocalPosContent=%s",dbLocalPosContent); // DEBUG REMOVE if (strchr(dbLocalPosContent, '=')) { gotCart = TRUE; cartParseOverHash(lastDbPosCart, cloneString(dbLocalPosContent)); // this function chews up input position = cloneString(cartUsualString(lastDbPosCart, "position", NULL)); //warn("gotCart position=%s",position); // DEBUG REMOVE // DEBUG REMOVE: //struct dyString *dbPosValue = newDyString(4096); //cartEncodeState(lastDbPosCart, dbPosValue); //warn("gotCart dbPosValue->string=[%s]",dbPosValue->string); } else { position = dbLocalPosContent; // old style value } } else { position = defaultPosition; // no value was set } } if (position == NULL) { position = cloneString(cartUsualString(cart, "position", NULL)); } /* default if not set at all, as would happen if it came from a URL with no * position. Otherwise tell them to go back to the gateway. Also recognize * "default" as specifying the default position. */ if (((position == NULL) || sameString(position, "default")) && (defaultPosition != NULL)) position = cloneString(defaultPosition); if (!gotCart) { cartSetBoolean(lastDbPosCart, "virtMode", FALSE); cartSetString(lastDbPosCart, "virtModeType", "default"); cartSetString(lastDbPosCart, "lastVirtModeType", "default"); cartSetString(lastDbPosCart, "position", position); cartSetString(lastDbPosCart, "nonVirtPosition", position); cartSetString(lastDbPosCart, "lastVirtModeExtra", ""); } if (pLastDbPosCart) *pLastDbPosCart = lastDbPosCart; return position; } void cartSetDbPosition(struct cart *cart, char *database, struct cart *lastDbPosCart) /* Set the 'position.db' variable in the cart.*/ { char dbPosKey[256]; safef(dbPosKey, sizeof dbPosKey, "position.%s", database); struct dyString *dbPosValue = newDyString(4096); cartEncodeState(lastDbPosCart, dbPosValue); cartSetString(cart, dbPosKey, dbPosValue->string); }