373b5c4187422a9584814dcfa324028fd2e50f14 chmalee Tue Aug 4 15:00:30 2026 -0700 Have the pre-finish hook return the rows it wrote, and fix the table display bugs that the real row data now lets us resolve, refs #37999 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> diff --git src/hg/hgHubConnect/trackHubWizard.c src/hg/hgHubConnect/trackHubWizard.c index 33aced2287c..6d444dc0ad5 100644 --- src/hg/hgHubConnect/trackHubWizard.c +++ src/hg/hgHubConnect/trackHubWizard.c @@ -1,266 +1,249 @@ /* trackHubWizard -- a user interface for creating a track hubs configuration files */ /* Copyright (C) 2019 The Regents of the University of California * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */ #include "common.h" #include "cart.h" #include "cheapcgi.h" #include "hdb.h" #include "hgConfig.h" #include "md5.h" #include "trashDir.h" #include "hgHubConnect.h" #include "jsHelper.h" #include "web.h" #include "wikiLink.h" #include "customTrack.h" #include "userdata.h" #include "jsonWrite.h" #include "cartJson.h" #include "hubSpace.h" #include "hubSpaceKeys.h" #include "hubConnect.h" #include "trackHub.h" #include "htmshell.h" #include <limits.h> #include "errCatch.h" void removeOneFile(char *userName, char *cgiFileName, char *fullPath, char *db, char *fileType) /* Remove one single file for userName */ { // prefixUserFile returns a canonicalized path, or NULL if the // canonicalized path does not begin with the hg.conf specified userDataDir // TODO: make the debug information from stderr go to stdout so the user // can know there is a mistake somewhere, and only print the debug // information in the event that the filename actually begins with the // userDataDir so we don't tell hackers what files do and do not exist char *fileName = prefixUserFile(userName, fullPath, NULL); if (fileName) { if (fileExists(fileName)) { fprintf(stderr, "deleting file: '%s'\n", fileName); removeFileForUser(fileName, userName); fflush(stderr); } else { fprintf(stderr, "file '%s' does not exist\n", fileName); fflush(stderr); } } } int pathDepth(char *path) { // replace multiple occurences of '/' with just a single one to get a canonical path // as path///to/file and path/to/file are the same path on Linux char *deduped = replaceChars(path, "//", "/"); return countChars(deduped, '/'); } int sortByFullPathCmp(const void *va, const void *vb) /* Compare two fullPaths */ { struct jsonElement *a = (struct jsonElement *)(*(struct slRef **)va)->val; struct jsonElement *b = (struct jsonElement *)(*(struct slRef **)vb)->val; char *aFullpath = jsonStringField(a, "fullPath"); char *bFullpath = jsonStringField(b, "fullPath"); int aDepth = pathDepth(aFullpath); int bDepth = pathDepth(bFullpath); // ensure subdirectories order before their parents: if (aDepth != bDepth) return bDepth - aDepth; // if equal depth than lexicographic sort is fine return strcmp(jsonStringField(a,"fullPath"), jsonStringField(b, "fullPath")); } void sortByFullPath(struct jsonElement *listJson) { slSort(&(listJson->val.jeList), sortByFullPathCmp); } void doRemoveFile(struct cartJson *cj, struct hash *paramHash) /* Process the request to remove a file */ { char *userName = getUserName(); if (userName) { // our array of objects, each object represents a track file struct jsonElement *deleteJson = hashFindVal(paramHash, "fileList"); struct slRef *copy, *f, *fileList = deleteJson->val.jeList; struct jsonElement *dirListJsonEle = newJsonList(NULL); jsonWriteListStart(cj->jw, "deletedList"); for (f = fileList; f != NULL; ) { struct jsonElement *fileObj = (struct jsonElement *)f->val; char *fileName = jsonStringField(fileObj, "fileName"); char *fileType = jsonStringField(fileObj, "fileType"); char *db = jsonStringField(fileObj, "genome"); char *fullPath = jsonStringField(fileObj, "fullPath"); copy = f->next; if (sameString(fileType, "dir")) { f->next = NULL; jsonListAdd(dirListJsonEle, fileObj); } else { if (sameString(fileType, "hub.txt")) { // disconnect this hub from the cart if it exists char *hubUrl = urlForFile(userName, fullPath); char *hubId = hubNameFromUrl(hubUrl); if (hubId) { /* remove the cart variable */ hubId += 4; // skip past the hub_ part char buffer[1024]; safef(buffer, sizeof buffer, "hgHubConnect.hub.%s", hubId); cartRemove(cj->cart, buffer); } } removeOneFile(userName, fileName, fullPath, db, fileType); // write out the fullPath so the DataTable can remove the correct row: jsonWriteString(cj->jw, NULL, fullPath); } f = copy; } // now attempt to delete any requested directories, but don't die if they still have contents sortByFullPath(dirListJsonEle); struct slRef *dir = NULL; for (dir = dirListJsonEle->val.jeList; dir != NULL; dir = dir->next) { struct jsonElement *fileObj = (struct jsonElement *)dir->val; char *fileName = jsonStringField(fileObj, "fileName"); char *fileType = jsonStringField(fileObj, "fileType"); char *db = jsonStringField(fileObj, "genome"); char *fullPath = jsonStringField(fileObj, "fullPath"); removeOneFile(userName, fileName, fullPath, db, fileType); // write out the fullPath so the DataTable can remove the correct row: jsonWriteString(cj->jw, NULL, fullPath); } jsonWriteListEnd(cj->jw); } } void doMoveFile(struct cartJson *cj, struct hash *paramHash) /* Move a file to a new hub */ { } static void outUiDataForUser(struct jsonWrite *jw) /* List out the currently stored files for the user as well as other data * needed to create the hubSpace table */ { char *userName = getUserName(); jsonWriteObjectStart(jw, "userFiles"); if (userName) { // the url for this user: jsonWriteString(jw, "userUrl", webDataDir(userName)); - jsonWriteListStart(jw, "fileList"); - struct hubSpace *file, *fileList = listFilesForUser(userName); - for (file = fileList; file != NULL; file = file->next) - { - jsonWriteObjectStart(jw, NULL); - jsonWriteString(jw, "fileName", file->fileName); - jsonWriteNumber(jw, "fileSize", file->fileSize); - jsonWriteString(jw, "fileType", file->fileType); - jsonWriteString(jw, "parentDir", file->parentDir); - jsonWriteString(jw, "genome", file->db); - jsonWriteString(jw, "lastModified", file->lastModified); - jsonWriteString(jw, "uploadTime", file->creationTime); - jsonWriteString(jw, "fullPath", stripDataDir(file->location, userName)); - jsonWriteString(jw, "md5sum", file->md5sum); - jsonWriteString(jw, "hubType", file->hubType ? file->hubType : "trackHub"); - jsonWriteObjectEnd(jw); - } - jsonWriteListEnd(jw); + hubSpaceWriteFileList(jw, userName, listFilesForUser(userName)); } jsonWriteBoolean(jw, "isLoggedIn", getUserName() ? TRUE : FALSE); jsonWriteString(jw, "hubNameDefault", defaultHubNameForUser(getUserName())); // if the user is not logged, the 0 for the quota is ignored jsonWriteNumber(jw, "userQuota", getUserName() ? checkUserQuota(getUserName()) : 0); jsonWriteNumber(jw, "maxQuota", getUserName() ? getMaxUserQuota(getUserName()) : HUB_SPACE_DEFAULT_QUOTA); jsonWriteObjectEnd(jw); } void getHubSpaceUIState(struct cartJson *cj, struct hash *paramHash) /* Get all the data we need to make a users hubSpace UI table. The cartJson library * deals with printing the json */ { outUiDataForUser(cj->jw); } void cjRevokeApiKey(struct cartJson *cj, struct hash *paramHash) /* Wrapper for cartJson to call lib function revokeApiKey, removes any api keys for the user */ { struct errCatch *errCatch = errCatchNew(); if (errCatchStart(errCatch)) { char *userName = getUserName(); hubSpaceRevokeApiKey(userName); } errCatchEnd(errCatch); if (!(errCatch->gotError)) jsonWriteString(cj->jw, "revoke", "true"); else jsonWriteStringf(cj->jw, "error", "revokeApiKey() error: '%s'", errCatch->message->string); errCatchFree(&errCatch); } void cjGenerateApiKey(struct cartJson *cj, struct hash *paramHash) /* Wrapper for cartJson to call lib function generateApiKey, makes a random (but not crypto-secure api key for use of hubtools to upload to hubspace, or for skipping cloudflare */ { struct errCatch *errCatch = errCatchNew(); char *apiKey = NULL; if (errCatchStart(errCatch)) { char *userName = getUserName(); apiKey = hubSpaceGenerateApiKey(userName); } errCatchEnd(errCatch); if (apiKey) jsonWriteString(cj->jw, "apiKey", apiKey); else if (errCatch->gotError) jsonWriteStringf(cj->jw, "error", "generateApiKey() error: '%s'", errCatch->message->string); errCatchFree(&errCatch); } void doTrackHubWizard(char *database) /* Offer an upload form so users can upload all their hub files */ { jsIncludeFile("utils.js", NULL); jsIncludeFile("ajax.js", NULL); jsIncludeFile("lodash.3.10.0.compat.min.js", NULL); jsIncludeFile("cart.js", NULL); jsIncludeFile("autocompleteCat.js",NULL); webIncludeResourceFile("font-awesome.min.css"); webIncludeResourceFile("dataTables-2.2.2.min.css"); jsIncludeFile("dataTables-2.2.2.min.js", NULL); webIncludeResourceFile("dataTables.buttons-3.2.2.min.css"); jsIncludeFile("dataTables.buttons-3.2.2.min.js", NULL); webIncludeResourceFile("dataTables.select-3.0.0.min.css"); jsIncludeFile("dataTables.select-3.0.0.min.js", NULL); puts("<link href=\"https://releases.transloadit.com/uppy/v4.5.0/uppy.min.css\" rel=\"stylesheet\">"); puts("<script type=\"text/javascript\" src=\"https://releases.transloadit.com/uppy/v4.5.0/uppy.min.js\"></script>"); jsIncludeFile("hgMyData.js", NULL); // the skeleton HTML: webIncludeFile("inc/hgMyData.html"); webIncludeResourceFile("hgMyData.css"); jsInlineF("\nvar isLoggedIn = %s;\n", getUserName() ? "true" : "false"); jsInlineF("\nvar cartDb=\"%s %s\";\n", trackHubSkipHubName(hGenome(database)), database); jsInlineF("\nvar tusdEndpoint=\"%s\";\n", cfgOptionDefault("hubSpaceTusdEndpoint", NULL)); jsInlineF("\nvar fileListEndpoint=\"%shgHubConnect\";\n", hLoginHostCgiBinUrl()); jsInlineF("\nvar loginHost=\"http%s://%s\";\n", loginUseHttps() ? "s" : "", wikiLinkHost()); jsInlineF("\nvar hubGenomeCollisionErrFrag=\"%s\";\n", HUB_GENOME_COLLISION_ERR_FRAG); jsInline("$(document).ready(function() {\nhubCreate.init();\n})"); puts("</div>"); }