ae843e42c15f576fcbf9cc4a3671e615cf905a96
jcasper
  Wed Jul 6 12:45:04 2016 -0700
Additional configuration options for session thumbnail creation and examples for same, refs #17469

diff --git src/hg/hgSession/hgSession.c src/hg/hgSession/hgSession.c
index 678e0e0..dad6f5d 100644
--- src/hg/hgSession/hgSession.c
+++ src/hg/hgSession/hgSession.c
@@ -817,62 +817,89 @@
 	  htmlEncode(sessionName), (shareSession ? "may" : "may not"),
 	  getSessionLink(encUserName, encSessionName),
 	  getSessionEmailLink(encUserName, encSessionName));
     cartCheckForCustomTracks(cart, dyMessage);
     }
 else
     dyStringPrintf(dyMessage,
 	  "Sorry, required table %s does not exist yet in the central "
 	  "database (%s).  Please ask a developer to create it using "
 	  "kent/src/hg/lib/namedSessionDb.sql .",
 	  namedSessionTable, sqlGetDatabase(conn));
 hDisconnectCentral(&conn);
 return dyStringCannibalize(&dyMessage);
 }
 
-void thumbnailAdd(char *encUserName, char *encSessionName, struct sqlConnection *conn)
-/* Create a thumbnail image for the gallery.  Leaks memory from a generated filename string,
- * plus a couple of dyStrings.  Returns without determining if the image creation
- * succeeded. */
+int thumbnailAdd(char *encUserName, char *encSessionName, struct sqlConnection *conn, struct dyString *dyMessage)
+/* Create a thumbnail image for the gallery.  If the necessary tools can't be found,
+ * add a warning message to dyMessage unless the hg.conf setting
+ * sessionThumbnail.suppressWarning is set to "on".
+ * Leaks memory from a generated filename string, plus a couple of dyStrings.
+ * Returns without determining if image creation succeeded (it happens in a separate
+ * thread); the return value is 0 if a message was added to dyMessage, otherwise it's 1. */
 {
 char query[4096];
 char **row;
 struct sqlResult *sr;
+
+char *suppressConvert = cfgOption("sessionThumbnail.suppress");
+if (suppressConvert != NULL && sameString(suppressConvert, "on"))
+    return 1;
+
+char *convertPath = cfgOption("sessionThumbnail.convertPath");
+if (convertPath == NULL)
+    convertPath = cloneString("convert");
+char convertTestCmd[4096];
+safef(convertTestCmd, sizeof(convertTestCmd), "which %s >& /dev/null", convertPath);
+int convertTestResult = system(convertTestCmd);
+if (convertTestResult != 0)
+    {
+    dyStringPrintf(dyMessage,
+         "Note: A thumbnail image for this session was not created because the ImageMagick convert "
+         "tool could not be found.  Please contact your mirror administrator to resolve this "
+         "issue, either by installing convert so that it is part of the webserver's PATH, "
+         "by adding the \"sessionThumbnail.convertPath\" option to the mirror's hg.conf file "
+         "to specify the path to that program, or by adding \"sessionThumbnail.suppress=on\" to "
+         "the mirror's hg.conf file to suppress this warning.<br>");
+    return 0;
+    }
+
 sqlSafef(query, sizeof(query),
     "select m.idx, n.firstUse from gbMembers m join namedSessionDb n on m.userName = n.userName "
     "where m.userName = \"%s\" and n.sessionName = \"%s\"",
     encUserName, encSessionName);
 sr = sqlGetResult(conn, query);
 row = sqlNextRow(sr);
 if (row == NULL)
     errAbort("cannot add session to gallery; user %s, session %s",
         encUserName, encSessionName);
 
 char *destFile = sessionThumbnailFilePath(row[0], encSessionName, row[1]);
 if (destFile != NULL)
     {
     struct dyString *hgTracksUrl = dyStringNew(0);
     addSessionLink(hgTracksUrl, encUserName, encSessionName, FALSE);
     struct dyString *renderUrl =
         dyStringSub(hgTracksUrl->string, "cgi-bin/hgTracks", "cgi-bin/hgRenderTracks");
     dyStringAppend(renderUrl, "&pix=640");
     char *renderCmd[] = {"wget", "-q", "-O", "-", renderUrl->string, NULL};
-    char *convertCmd[] = {"convert", "-", "-resize", "320", "-crop", "320x240+0+0", destFile, NULL};
+    char *convertCmd[] = {convertPath, "-", "-resize", "320", "-crop", "320x240+0+0", destFile, NULL};
     char **cmdsImg[] = {renderCmd, convertCmd, NULL};
     pipelineOpen(cmdsImg, pipelineWrite, "/dev/null", NULL);
     }
 sqlFreeResult(&sr);
+return 1;
 }
 
 
 void thumbnailRemove(char *encUserName, char *encSessionName, struct sqlConnection *conn)
 /* Unlink thumbnail image for the gallery.  Leaks memory from a generated filename string. */
 {
 char query[4096];
 char **row;
 struct sqlResult *sr;
 sqlSafef(query, sizeof(query),
     "select m.idx, n.firstUse from gbMembers m join namedSessionDb n on m.userName = n.userName "
     "where m.userName = \"%s\" and n.sessionName = \"%s\"",
     encUserName, encSessionName);
 sr = sqlGetResult(conn, query);
 row = sqlNextRow(sr);
@@ -1037,31 +1064,31 @@
 
 	if (newGallery != inGallery)
 	    {
 	    sqlSafef(query, sizeof(query), "UPDATE %s SET shared = %d "
 		  "WHERE userName = '%s' AND sessionName = '%s';",
 		  namedSessionTable, newGallery == TRUE ? 2 : 1, encUserName, encSessionName);
 	    sqlUpdate(conn, query);
 	    sessionTouchLastUse(conn, encUserName, encSessionName);
 	    dyStringPrintf(dyMessage,
 			   "Marked session <B>%s</B> as %s.<BR>\n",
 			   htmlEncode(sessionName),
 			   (newGallery == TRUE ? "added to gallery" : "removed from public listing"));
         if (newGallery == FALSE)
             thumbnailRemove(encUserName, encSessionName, conn);
         if (newGallery == TRUE)
-            thumbnailAdd(encUserName, encSessionName, conn);
+            thumbnailAdd(encUserName, encSessionName, conn, dyMessage);
 	    didSomething = TRUE;
 	    }
 	}
     hashFree(&galleryHash);
     }
 
 cartHelList = cartFindPrefix(cart, hgsSharePrefix);
 if (cartHelList != NULL)
     {
     struct hash *sharedHash = hashNew(0);
     char **row;
     struct sqlResult *sr;
     sqlSafef(query, sizeof(query),
 	  "select sessionName,shared from %s where userName = '%s'",
 	  namedSessionTable, encUserName);
@@ -1341,58 +1368,58 @@
     char *encNewName = cgiEncodeFull(newName);
     sqlSafef(query, sizeof(query),
 	  "UPDATE %s set sessionName = '%s' WHERE userName = '%s' AND sessionName = '%s';",
 	  namedSessionTable, encNewName, encUserName, encSessionName);
 	sqlUpdate(conn, query);
     dyStringPrintf(dyMessage, "Changed session name from %s to <B>%s</B>.\n",
 		   sessionName, newName);
     sessionName = newName;
     encSessionName = encNewName;
     renamePrefixedCartVar(hgsEditPrefix, encOldSessionName, encNewName);
     renamePrefixedCartVar(hgsLoadPrefix, encOldSessionName, encNewName);
     renamePrefixedCartVar(hgsDeletePrefix, encOldSessionName, encNewName);
     if (shared >= 2)
         {
         thumbnailRemove(encUserName, encSessionName, conn);
-        thumbnailAdd(encUserName, encNewName, conn);
+        thumbnailAdd(encUserName, encNewName, conn, dyMessage);
         }
     }
 
 char sharedVarName[256];
 char galleryVarName[256];
 safef(sharedVarName, sizeof(sharedVarName), hgsSharePrefix "%s", encOldSessionName);
 safef(galleryVarName, sizeof(galleryVarName), hgsGalleryPrefix "%s", encOldSessionName);
 if (cgiBooleanDefined(sharedVarName) || cgiBooleanDefined(galleryVarName))
     {
     int newShared = shared;
     if (cgiBooleanDefined(sharedVarName))
         newShared = cartBoolean(cart, sharedVarName) ? 1 : 0;
     if (cgiBooleanDefined(galleryVarName))
         newShared = cartBoolean(cart, galleryVarName) ? 2 : newShared;
     if (newShared != shared)
         {
         sqlSafef(query, sizeof(query),
             "UPDATE %s set shared = %d WHERE userName = '%s' AND sessionName = '%s';",
             namedSessionTable, newShared, encUserName, encSessionName);
         sqlUpdate(conn, query);
         dyStringPrintf(dyMessage, "Marked session <B>%s</B> as %s.<BR>\n",
             htmlEncode(sessionName), (newShared>0 ? newShared>=2 ? "shared in public listing" :
                 "shared, but not in public listing" : "unshared"));
         if (shared >= 2 && newShared < 2)
             thumbnailRemove(encUserName, encSessionName, conn);
         if (shared < 2 && newShared >= 2)
-            thumbnailAdd(encUserName, encSessionName, conn);
+            thumbnailAdd(encUserName, encSessionName, conn, dyMessage);
         }
     cartRemove(cart, sharedVarName);
     cartRemove(cart, galleryVarName);
     char shadowVarName[512];
     safef(shadowVarName, sizeof(shadowVarName), "%s%s", cgiBooleanShadowPrefix(), sharedVarName);
     cartRemove(cart, shadowVarName);
     safef(shadowVarName, sizeof(shadowVarName), "%s%s", cgiBooleanShadowPrefix(), galleryVarName);
     cartRemove(cart, shadowVarName);
     }
 if (gotSettings)
     {
     struct hash *settingsHash = raFromString(settings);
     char *description = hashFindVal(settingsHash, "description");
     char *newDescription = cartOptionalString(cart, hgsNewSessionDescription);
     if (newDescription != NULL)