80ab15c036bf35894b59df383c03c51cbeff30be chmalee Thu May 14 10:29:53 2026 -0700 Stop myVariants from clobbering regular custom tracks by giving it its own cart variable (mvCtfile_<db>) instead of overloading ctfile_<db>, refs #37553 diff --git src/hg/lib/customTrack.c src/hg/lib/customTrack.c index 0aac5925f5c..198a98ade7f 100644 --- src/hg/lib/customTrack.c +++ src/hg/lib/customTrack.c @@ -541,30 +541,33 @@ FILE *f = mustOpen(fileName, "w"); #ifdef DEBUG struct dyString *ds = dyStringNew(100); if (!fileExists(fileName)) { dyStringPrintf(ds, "chmod 666 %s", fileName); system(ds->string); } #endif struct customTrack *track; struct dyString *ds = dyStringNew(0); for (track = trackList; track != NULL; track = track->next) { + /* myVariants tracks live in mvCtfile_<db>; skip so they aren't duplicated here. */ + if (track->tdb && track->tdb->type && startsWithWord("myVariants", track->tdb->type)) + continue; /* may be coming in here from the table browser. It has wiggle * ascii data waiting to be encoded into .wib and .wig */ if (track->wigAscii) { /* HACK ALERT - calling private method function in customFactory.c */ track->maxChromName = hGetMinIndexLength(genomeDb); /* for the loaders */ #ifdef PROGRESS_METER track->progressFile = 0; #endif wigLoaderEncoding(track, track->wigAscii, ctDbUseAll()); ctAddToSettings(track, "tdbType", track->tdb->type); ctAddToSettings(track, "wibFile", track->wibFile); } @@ -588,43 +591,51 @@ if (!track->dbTrack) { struct bed *bed; for (bed = track->bedList; bed != NULL; bed = bed->next) bedOutputN(bed, track->fieldCount, f, '\t', '\n'); } } dyStringFree(&ds); carefulClose(&f); } void customTracksSaveCart(char *genomeDb, struct cart *cart, struct customTrack *ctList) /* Save custom tracks to trash file for database in cart */ { char *ctFileVar = customTrackFileVar(cartString(cart, "db")); -if (ctList) +boolean hadRegular = isNotEmpty(cartOptionalString(cart, ctFileVar)); +/* Count only tracks that customTracksSaveFile will actually persist. */ +int persistCount = 0; +struct customTrack *ct; +for (ct = ctList; ct != NULL; ct = ct->next) + if (!(ct->tdb && ct->tdb->type && startsWithWord("myVariants", ct->tdb->type))) + persistCount++; +if (persistCount > 0) { static struct tempName tn; trashDirFile(&tn, "ct", CT_PREFIX, ".bed"); char *ctFileName = tn.forCgi; cartSetString(cart, ctFileVar, ctFileName); if (printSaveList) - fprintf(stderr, "customTrack: saved %d in %s\n", slCount(ctList), ctFileName); + fprintf(stderr, "customTrack: saved %d in %s\n", persistCount, ctFileName); customTracksSaveFile(genomeDb, ctList, ctFileName); } -else +else if (hadRegular) { - /* no custom tracks remaining for this assembly */ + /* user just emptied this assembly's regular ctfile; drop the pointer + * and the per-track ct_* cart vars tied to it. */ cartRemove(cart, ctFileVar); cartRemovePrefix(cart, CT_PREFIX); } } boolean customTrackIsCompressed(char *fileName) /* test for file suffix indicating compression */ { char *fileNameDecoded = cloneString(fileName); cgiDecode(fileName, fileNameDecoded, strlen(fileName)); boolean result = (endsWith(fileNameDecoded,".gz") || endsWith(fileNameDecoded,".Z") || endsWith(fileNameDecoded,".zip") || endsWith(fileNameDecoded,".bz2")); @@ -938,82 +949,98 @@ errCatch->message->string[len - 1] = 0; } warn("Custom track loading error (%s): failed to load custom tracks. " "This is a temporary internal error, please refresh your browser. If you continue to experience this issue" "please reach out to genome-www@soe.ucsc.edu and send us a session link " "where this error occurs", errCatch->message->string); loadFailed = TRUE; } errCatchFree(&errCatch); // If there was a failure in loading the custom tracks, return immediately -- don't try to // add or merge in new custom tracks. The cartRemove statements below will be skipped, so we // can try again next click. if (loadFailed) return NULL; + } + +/* Layer in the user's myVariants tracks from mvCtfile_<db>. */ +char mvVar[256]; +safef(mvVar, sizeof mvVar, MYVARIANTS_FILE_VAR_PREFIX "%s", genomeDb); +char *mvFile = cartOptionalString(cart, mvVar); +if (isNotEmpty(mvFile) && fileExists(mvFile)) + { + struct customTrack *mvList = NULL; + struct errCatch *mvCatch = errCatchNew(); + if (errCatchStart(mvCatch)) + mvList = customFactoryParse(genomeDb, mvFile, TRUE, fileName, NULL); + errCatchEnd(mvCatch); + if (mvCatch->gotError) + { + if (isNotEmpty(mvCatch->message->string)) + warn("myVariants load error: %s", mvCatch->message->string); + cartRemove(cart, mvVar); + mvList = NULL; + } + errCatchFree(&mvCatch); + ctList = slCat(ctList, mvList); + } - /* handle selected tracks -- update doc, remove, etc. */ +/* Handle a selected track from hgTrackUi (remove or doc update). */ char *selectedTable = NULL; if (cartVarExists(cart, CT_DO_REMOVE_VAR)) selectedTable = cartOptionalString(cart, CT_SELECTED_TABLE_VAR); else selectedTable = cartOptionalString(cart, CT_UPDATED_TABLE_VAR); if (selectedTable) { for (ct = ctList; ct != NULL; ct = nextCt) { nextCt = ct->next; if (sameString(selectedTable, ct->tdb->track)) { if (cartVarExists(cart, CT_DO_REMOVE_VAR)) { - /* remove a track if requested, e.g. by hgTrackUi */ removedCt = TRUE; - /* myVariants tracks need type-specific cleanup so the - * SQL-backed entry doesn't reappear next page load. - * When the helper handled the cart cleanup itself we - * skip the wide cartRemovePrefix below so other- - * assembly per-db labels survive. */ + /* myVariants has SQL-backed state to clean up; helper returns + * TRUE when it handled the per-track cart cleanup itself. */ boolean handledByMyVariants = myVariantsHandleCtRemoval(ct, cart, genomeDb); slRemoveEl(&ctList, ct); if (!handledByMyVariants) { - /* remove visibility variable */ cartRemove(cart, selectedTable); - /* remove configuration variables */ char buf[128]; safef(buf, sizeof buf, "%s.", selectedTable); cartRemovePrefix(cart, buf); } cartRemove(cart, CT_DO_REMOVE_VAR); } else { if (html && differentString(html, ct->tdb->html)) { ct->tdb->html = html; changedCt = TRUE; } } break; } } } cartRemove(cart, CT_DO_REMOVE_VAR); cartRemove(cart, CT_SELECTED_TABLE_VAR); - } /* merge new and old tracks */ numAdded = slCount(newCts); /* add delay even if numAdded==0 because that can be when the loading * of the custom tracks failed. The try is worth the penalty. */ if (numAdded > 0) { static int botCheckMult = 0; if (0 == botCheckMult) // only on first time through here { // default is 1 when not specified char *val = cfgOptionDefault("customTracks.botCheckMult", "1"); botCheckMult = sqlSigned(val); if (botCheckMult < 1) // protect against negative value botCheckMult = 1; // default is 1, no maximum check here