7ba5c812bda048b28ade940e0030b8000a02ae4b chmalee Mon Aug 10 11:58:51 2026 -0700 hubSpace: key rows on location so two hubs can hold the same file name, refs #37964 Co-Authored-By: Claude Opus 5 (1M context) diff --git src/hg/hgHubConnect/hooks/pre-finish.c src/hg/hgHubConnect/hooks/pre-finish.c index 4443d1810c2..d41b1aebe6f 100644 --- src/hg/hgHubConnect/hooks/pre-finish.c +++ src/hg/hgHubConnect/hooks/pre-finish.c @@ -196,86 +196,84 @@ row->location = canonicalPath; else { // all upload data should have been received and thus the realpath // should not fail, but just in case, put something valid here row->location = tusFile; } row->md5sum = md5HexForFile(row->location); row->parentDir = encodedParentDir ? encodedParentDir : ""; // Derive hubType server-side; never trust the client's hubType. // A 2bit always promotes its hub to assembly. Otherwise inherit // the existing hub's type, defaulting to trackHub. // both lookups below are about the hub as a whole, whose row and hub.txt // live at the top level, so use the hub component of parentDir char *parentDirForCheck = encodedParentDir ? hubRootFromParentDir(encodedParentDir) : ""; + char *hubDir = encodedParentDir ? + hubPathFromParentDir(encodedParentDir, userDataDir) : NULL; if (sameOk(fileType, "2bit")) row->hubType = "assemblyHub"; else { char *existingType = existingHubTypeForDir(userName, parentDirForCheck); row->hubType = existingType ? existingType : "trackHub"; } char *batchHasHubTxtStr = jsonQueryString(req, "", "Event.Upload.MetaData.batchHasHubTxt", NULL); boolean batchHasHubTxt = sameOk(batchHasHubTxtStr, "true"); - boolean userOwnNamedHubTxt = userHasOwnNamedHubTxtInDir(userName, parentDirForCheck); + boolean userOwnNamedHubTxt = userHasOwnNamedHubTxtInDir(userName, parentDirForCheck, hubDir); boolean userAuth = batchHasHubTxt || userOwnNamedHubTxt; boolean isHubTxt = sameOk(fileType, "hub.txt"); boolean isTwoBit = sameOk(fileType, "2bit"); // Serialize hub.txt read-modify-write across parallel pre-finish // processes for the same hub. flock is held for the entire // decision + action so writeHubText's fileExists check and the // upgrade's read-rewrite are atomic with respect to siblings. // Lock the directory the hub.txt is in, so uploads into different // subdirectories of one hub still serialize against each other. int hubLockFd = -1; - if (encodedParentDir) - { - char *hubDir = hubPathFromParentDir(encodedParentDir, userDataDir); + if (hubDir) hubLockFd = lockHubDir(hubDir); - freeMem(hubDir); - } if (!isHubToolsUpload && !isHubTxt) { if (!userAuth) { if (isTwoBit) { // createNewTempHubForUpload is a no-op when the hub.txt and its // row are already there, and it backfills the row when they are not createNewTempHubForUpload(reqId, row, userDataDir); - upgradeExistingHubToAssembly(row, userDataDir, encodedParentDir); + upgradeExistingHubToAssembly(row, userDataDir); } else createNewTempHubForUpload(reqId, row, userDataDir); } else if (isTwoBit) { // user's hub.txt is authoritative; just flip rows to assemblyHub. - upgradeExistingHubToAssembly(row, userDataDir, encodedParentDir); + upgradeExistingHubToAssembly(row, userDataDir); } } // still under the hub lock: makeParentDirRows checks for a row and then // inserts it, so two uploads to one hub would otherwise both insert the // same directory row // first make the parentDir rows // the directory rows carry the upload's own timestamp. row->lastModified // holds it as a GMT clock string, which sqlDateToUnixTime would read as // local time, so pass the seconds directly makeParentDirRows(row->userName, lastModified, row->db, row->parentDir, userDataDir, row->hubType); - row->parentDir = encodedParentDir ? hubNameFromPath(encodedParentDir) : ""; + row->parentDir = encodedParentDir ? hubLeafFromPath(encodedParentDir) : ""; addHubSpaceRowForFile(row); unlockHubDir(hubLockFd); fprintf(stderr, "added hubSpace row for file '%s'\n", fileName); fflush(stderr); // Send the client the hub as it now stands, so it can show the new file, // its directories and the hub.txt. The upload has already succeeded at // this point, so catch errors here rather than let them reject it: the // worst case is a client that shows nothing new until the page is reloaded struct errCatch *respCatch = errCatchNew(0); if (errCatchStart(respCatch)) { if (encodedParentDir) { char *hubName = hubRootFromParentDir(encodedParentDir);