af6898753ba6d5f3872957cd270872ce4c4e3dfc
max
  Tue Apr 11 08:19:27 2023 -0700
tolerate if browser CSS theme files do not exist, cleaning up the resource link mess a little, refs #7867

diff --git src/hg/lib/web.c src/hg/lib/web.c
index 360ff9b..58247fc 100644
--- src/hg/lib/web.c
+++ src/hg/lib/web.c
@@ -1207,30 +1207,74 @@
     }
 }
 
 void webFinishPartialLinkOutTable(int rowIx, int itemPos, int maxPerRow)
 /* Fill out partially empty last row. */
 {
 finishPartialTable(rowIx, itemPos, maxPerRow, webPrintLinkOutCellStart);
 }
 
 void webFinishPartialLinkTable(int rowIx, int itemPos, int maxPerRow)
 /* Fill out partially empty last row. */
 {
 finishPartialTable(rowIx, itemPos, maxPerRow, webPrintLinkCellStart);
 }
 
+static struct dyString* getHtdocsSubdir(char *dirName)
+/* return dystring with subdirectory under htDocs, tolerant of missing docRoot */
+{
+struct dyString *fullDirName = NULL;
+char *docRoot = hDocumentRoot();
+if (docRoot != NULL)
+    fullDirName = dyStringCreate("%s/%s", docRoot, dirName);
+else
+    // tolerate missing docRoot (i.e. when running from command line)
+    fullDirName = dyStringCreate("%s", dirName);
+return fullDirName;
+}
+
+char *webCssLink(char *fileName, boolean mustExist)
+/* alternative for webTimeStampedLinkToResource for CSS files: returns a string with a time-stamped
+ * link to a CSS file as a html fragment <link .... >. returns empty string if file does not exist.
+ * */
+{
+// construct the absolute path to the file on disk
+char *relDir = cfgOptionDefault("browser.styleDir","style");
+struct dyString *absDir = getHtdocsSubdir(relDir);
+struct dyString *absFileName = dyStringCreate("%s/%s", dyStringContents(absDir), fileName);
+
+struct dyString *htmlFrag = NULL;
+if (!fileExists(dyStringContents(absFileName)))
+    {
+    if (mustExist)
+        errAbort("webTimeStampedLinkToResource: file: %s doesn't exist.\n", 
+                dyStringContents(absFileName));
+    else
+        htmlFrag = dyStringNew(0);
+    }
+else
+    {
+    // construct a link with the relative path to the file on the web server
+    long mtime = fileModTime(dyStringContents(absFileName));
+    htmlFrag = dyStringCreate("<link rel='stylesheet' href='../%s/%s?v=%ld' type='text/css'>\n", relDir, fileName, mtime);
+    }
+
+dyStringFree(&absFileName);
+dyStringFree(&absDir);
+return dyStringContents(htmlFrag);
+}
+
 char *webTimeStampedLinkToResource(char *fileName, boolean wrapInHtml)
 // If wrapInHtml
 //   returns versioned link embedded in style or script html (free after use).
 // else
 //   returns full path of a versioned path to the requested resource file (js, or css).
 // NOTE: png, jpg and gif should also be supported but are untested.
 //
 // In production sites we use a versioned softlink that includes the CGI version. This has the following benefits:
 // a) flushes user's web browser cache when the user visits a GB site whose version has changed since their last visit;
 // b) enforces the requirement that static files are the same version as the CGIs (something that often fails to happen in mirrors).
 // (see notes in redmine #3170).
 //
 // In dev trees we use mtime to create a pseudo-version; this forces web browsers to reload css/js file when it changes,
 // so we don't get odd behavior that can be caused by caching of mis-matched javascript and style files in dev trees.
 //
@@ -1248,37 +1292,39 @@
                 || sameString(".jpg",extension)
                 || sameString(".gif",extension));
 if (!js && !style) // && !image) NOTE: This code has not been tested on images but should work.
     errAbort("webTimeStampedLinkToResource: unknown resource type for %s.\n", fileName);
 
 char *httpHost = hHttpHost();
 
 // Build and verify directory
 char *dirName = "";
 if (js)
     dirName = cfgOptionDefault("browser.javaScriptDir", "js");
 else if (style)
     dirName = cfgOptionDefault("browser.styleDir","style");
 else if (image)
     dirName = cfgOptionDefault("browser.styleImagesDir","style/images");
+
 struct dyString *fullDirName = NULL;
 char *docRoot = hDocumentRoot();
 if (docRoot != NULL)
     fullDirName = dyStringCreate("%s/%s", docRoot, dirName);
 else
     // tolerate missing docRoot (i.e. when running from command line)
     fullDirName = dyStringCreate("%s", dirName);
+
 if (!fileExists(dyStringContents(fullDirName)))
     errAbort("webTimeStampedLinkToResource: dir: %s doesn't exist. (host: %s)\n",
              dyStringContents(fullDirName), httpHost);
 
 // build and verify real path to file
 struct dyString *realFileName = dyStringCreate("%s/%s", dyStringContents(fullDirName), fileName);
 if (!fileExists(dyStringContents(realFileName)))
     errAbort("webTimeStampedLinkToResource: file: %s doesn't exist.\n",
              dyStringContents(realFileName));
 
 // build and verify link path including timestamp in the form of dir/baseName + timeStamp or CGI Version + ext
 long mtime = fileModTime(dyStringContents(realFileName));
 struct dyString *linkWithTimestamp;
 
 linkWithTimestamp = dyStringCreate("%s/%s%s?v=%ld", dyStringContents(fullDirName), baseName, extension, mtime);