Index: main/trunk/greenstone3/web/interfaces/default/js/javascript-global-functions.js
===================================================================
--- main/trunk/greenstone3/web/interfaces/default/js/javascript-global-functions.js	(revision 31542)
+++ main/trunk/greenstone3/web/interfaces/default/js/javascript-global-functions.js	(revision 31543)
@@ -592,5 +592,5 @@
 // behaviour of calling set-metadata-array. E.g. where=import|archives|index
 // THIS METHOD IS SYNCHRONOUS
-gs.functions.setMetadataArray = function(collection, site, docArray, metamode, where, responseFunction) 
+gs.functions.setMetadataArray = function(collection, site, docArray, metamode, where, responseFunction, forceSync) 
 {
     docArrayJSON = JSON.stringify(docArray);
@@ -610,6 +610,11 @@
     }
     
-
-    var response = callMetadataServer("Setting metadata in "+where, "cgi-bin/metadata-server.pl?"+params, responseFunction);
+    // set operations are generally synchronous, but allow calling function to force ajax call 
+    // to be synchronous or not. Default is synchronous, as it was for GS2
+    if(forceSync == null) {
+	forceSync = true;
+    }
+
+    var response = callMetadataServer("Setting metadata in "+where, "cgi-bin/metadata-server.pl?"+params, responseFunction, {"forceSync": forceSync});
 
     return response;
@@ -625,5 +630,5 @@
 // See description for setMetadataArray above for information about the 'where' parameter.
 // THIS METHOD IS SYNCHRONOUS BY DEFAULT. Set forceSync to false to override this default behaviour
-gs.functions.getMetadataArray = function(collection, site, docArray, where, forceSync, responseFunction)
+gs.functions.getMetadataArray = function(collection, site, docArray, where, responseFunction, forceSync)
 {
     docArrayJSON = JSON.stringify(docArray);
Index: main/trunk/greenstone3/web/interfaces/default/js/user_comments.js
===================================================================
--- main/trunk/greenstone3/web/interfaces/default/js/user_comments.js	(revision 31542)
+++ main/trunk/greenstone3/web/interfaces/default/js/user_comments.js	(revision 31543)
@@ -4,5 +4,5 @@
 // http://stackoverflow.com/questions/256754/how-to-pass-arguments-to-addeventlistener-listener-function
 // http://stackoverflow.com/questions/8714472/cant-pass-event-to-addeventlistener-closure-issue
-
+// https://www.sitepoint.com/demystifying-javascript-closures-callbacks-iifes/
 
 /***************
@@ -18,63 +18,85 @@
 
 function loadUserComments() {
-    /*
-
-    if(gs.variables) {
-	var listing = "";
-	for (prop in gs.variables) {
-	    listing = listing + prop.toString() + ": " + gs.variables[prop] + "\n";
-	}
-	alert ("gs variables:\n" + listing);
-    } else {
-	alert("LOADING USER COMMENTS - no gs.variables set");
-    }
-    */
-    
-    // don't bother loading comments if we're not on a document page (in which case there's no usercommentdiv)
+
+    // don't bother loading comments if we're not on a document page (in which case there's no docid)
+    var doc_id = gs.variables["d"]; ///"_cgiargdJssafe_"; //escape("cgiargd");
+
+    // stackoverflow.com/questions/36661748/what-is-the-exact-negation-of-ifvariable-in-javascript
+    if(!doc_id) { // vs http://stackoverflow.com/questions/784929/what-is-the-not-not-operator-in-javascript
+	
+	return;
+    }
+
+    // Don't allow the user to add comments (which calls set-meta-array) until we've finished loading all 
+    // user comments (which calls get-meta-array). Since get-meta-array and set-meta-array are called 
+    // asynchronously, this is to help prevent any overlap in these functions' access of meta files in
+    // index|archives|import.
+    var submitButton = document.getElementById("usercommentSubmitButton");    
+    if(submitButton) { // there'll be no submitButton if the comment form is not displayed (user not logged in)
+	submitButton.disabled = true;
+    }
+
+    
+    // don't allow users to add comments (disable the submit button)
+    // until existing comments have been retrieved and displayed
+    //document.getElementById("usercommentSubmitButton").disabled = true;
+
+    // Since we have a docid, get toplevel section of the docid
+    
+    var period = doc_id.indexOf(".");
+    if(period != -1) {
+	doc_id = doc_id.substring(0, period);
+    }
+    
+    var username_rec = {
+	metaname: "username",
+	metapos: "all"
+    };
+    
+    var timestamp_rec = {
+	metaname: "usertimestamp",
+	metapos: "all"
+    };
+    
+    var comment_rec = {
+	metaname: "usercomment",
+	metapos: "all"
+    };
+
+    var doc_rec = {
+	docid: doc_id,
+	metatable: [username_rec, timestamp_rec, comment_rec]	    
+    };
+
+    var docArray = [doc_rec];
+    //alert(JSON.stringify(docArray));
+
+    //var json_result_str = gs.functions.getMetadataArray(gs.variables["c"], gs.variables["site"], docArray, "index");
+
+    gs.functions.getMetadataArray(gs.variables["c"], gs.variables["site"], docArray, "index", loadedUserComments, false); // false for asynchronous
+}
+
+function loadedUserComments(xmlHttpObj)
+{
+    // don't bother displaying comments if we're not on a document page
+    // (in which case there's no usercommentdiv).
+    // This shouldn't happen here, since we only call this from loadUserComments()
+    // and that first checks we're actually on a document page.
     var usercommentdiv = document.getElementById("usercomments");
     if(usercommentdiv == undefined || usercommentdiv == null) {
 	return;
     }
-    
-    // else, if we have a usercommentdiv, we would have a docid. Get toplevel section of the docid
-    var doc_id = gs.variables["d"]; ///"_cgiargdJssafe_"; //escape("cgiargd");
-    var period = doc_id.indexOf(".");
-    if(period != -1) {
-	doc_id = doc_id.substring(0, period);
-    }
-    
-    var username_rec = {
-	metaname: "username",
-	metapos: "all"
-    };
-    
-    var timestamp_rec = {
-	metaname: "usertimestamp",
-	metapos: "all"
-    };
-    
-    var comment_rec = {
-	metaname: "usercomment",
-	metapos: "all"
-    };
-
-    var doc_rec = {
-	docid: doc_id,
-	metatable: [username_rec, timestamp_rec, comment_rec]	    
-    };
-
-    var docArray = [doc_rec];
-    //alert(JSON.stringify(docArray));
-
-    var json_result_str = gs.functions.getMetadataArray(gs.variables["c"], gs.variables["site"], docArray, "index");
+
+    var json_result_str = xmlHttpObj.responseText;
     //	alert(json_result_str);
+    console.log("Got to display: " + json_result_str);
     var result = JSON.parse(json_result_str);
     // result contains only one docrec (result[0]), since we asked for the usercomments of one docid
     var metatable = result[0].metatable;
     //	alert(JSON.stringify(metatable));
-
+    
     var i = 0;
     var looping = true;
-
+    
     
     // if there's at least one existing comment OR if the form is currently being displayed
@@ -90,5 +112,5 @@
     }
     
-   
+    
     // metatable[0] = list of usernames, metatable[1] = list of timestamps, metatable[2] = list of comments	
     // the 3 lists/arrays should be of even length. Assuming this, loop as long as there's another username
@@ -97,5 +119,5 @@
 	if(metaval_rec == undefined) {
 	    looping = false;
-	    } 
+	} 
 	else {
 	    
@@ -117,5 +139,10 @@
 	}		
     }
-
+    
+    var submitButton = document.getElementById("usercommentSubmitButton");
+    // Now we've finished loading all user comments, allow the user to add a comment
+    if(submitButton) {
+	submitButton.disabled = false;
+    }
 }
 
@@ -174,6 +201,6 @@
     if(!trimmed_comment) { // || !trimmed_username
 	doc.AddUserCommentForm.comment.value = "";		      
-	//doc.AddUserCommentForm.username.value = "";
-	doc.getElementById("usercommentfeedback").innerHTML = gs.variables["textisempty"]; ///"_textisempty_";
+	//document.AddUserCommentForm.username.value = "";
+	document.getElementById("usercommentfeedback").innerHTML = gs.variables["textisempty"]; ///"_textisempty_";
 	return;
     }
@@ -248,10 +275,26 @@
     //alert(JSON.stringify(docArray));
     
-    // GSAPI already knows the collection
-    var result = gs.functions.setMetadataArray(gs.variables["c"], gs.variables["site"], docArray, "accumulate", "import|archives|index");
-    
+    // Don't allow the user to submit further comments until the metadata has been updated
+    document.getElementById("usercommentSubmitButton").disabled = true;
+
+    
+    //var result = gs.functions.setMetadataArray(gs.variables["c"], gs.variables["site"], docArray, "accumulate", "import|archives|index");
+    gs.functions.setMetadataArray(gs.variables["c"], gs.variables["site"], docArray, "accumulate", "import|archives|index", function(xmlHttpObj) { return doneUpdatingMetatada(xmlHttpObj, _username, _timestamp, _comment); }, false); // false for asynchronous, 
+    // this is ok since we're disabling the comment submit button, so no further set-meta-array calls can be
+    // made until the ajax call returns and the callback is called which re-enables the submit button
+    // But disabling submit does not protect against concurrent access such as someone else editing the 
+    // document (doing set-meta operations, updating archives/index/import) at the same time as someone else
+    // adding user comments (doing set-meta-array, updating archives|index|import).
+
+}
+
+function doneUpdatingMetatada(xmlHttpObj, _username, _timestamp, _comment)
+{
+    var result = xmlHttpObj.responseText;
+    //alert("Received post response to setMeta: " + result); // just the HTML page
+
     // clear the comment field as it has now been submitted, but not the username field
     // as the user is logged in, so they should be able to commit again under their username.
-    doc.AddUserCommentForm.comment.value = ""; 
+    document.AddUserCommentForm.comment.value = ""; 
     
     // check for locked collection error
@@ -264,5 +307,5 @@
 	var error = result.substring(errorIndex,endIndex);
 	errormessage="ERROR: Unable to add comment. " + error;
-	doc.getElementById("usercommentfeedback").innerHTML = errormessage;
+	document.getElementById("usercommentfeedback").innerHTML = errormessage;
 	//alert("Result: " + result);
     } 
@@ -272,9 +315,9 @@
 	var error = result.substring(startIndex+1,endIndex);
 	errormessage="ERROR: Unable to add comment. " + error;
-	doc.getElementById("usercommentfeedback").innerHTML = errormessage;
+	document.getElementById("usercommentfeedback").innerHTML = errormessage;
 	//alert("Result: " + result);
     }
     else { // success!
-	doc.getElementById("usercommentfeedback").innerHTML = gs.variables["textcommentsubmitted"]; ///"_textcommentsubmitted_";		
+	document.getElementById("usercommentfeedback").innerHTML = gs.variables["textcommentsubmitted"]; ///"_textcommentsubmitted_";		
 	
 	// update display of existing user comments to show the newly added comment
@@ -283,9 +326,14 @@
 	    displayInUserCommentList(usercommentdiv, _username, _timestamp, _comment);
      	}
-    }     
+    }
+
+    // whether there was an error or not, re-enable the submit button now
+    // that the set-meta-array operation has completed.
+    document.getElementById("usercommentSubmitButton").disabled = false;
 }
 
 function commentAreaSetup() {
     loadUserComments();
+
     //$("div#commentarea").html("<textarea required=\"required\" name=\"comment\" rows=\"10\" cols=\"64\" placeholder=\"Add your comment here...\"></textarea>");
     $("div#commentarea").html('<textarea required="required" name="comment" rows="10" cols="64" placeholder="Add your comment here..."></textarea>');
@@ -296,21 +344,6 @@
 // "Handlers added via $(document).ready() don't overwrite each other, but rather execute in turn"
 // as explained at http://stackoverflow.com/questions/15564029/adding-to-window-onload-event
-
+// This way we ensure we don't replace any other onLoad() functions, but append the loadUserComments() 
+// function to the existing set of eventhandlers called onDocReady
 $(document).ready(commentAreaSetup);
 
-/*
-// http://stackoverflow.com/questions/807878/javascript-that-executes-after-page-load
-// ensure we don't replace any other onLoad() functions, but append the loadUserComments() 
-// function to the existing onLoad handlers
-
-if(window.onload) {
-    var curronload = window.onload;
-    var newonload = function() {
-        curronload();
-        loadUserComments();
-    };
-    window.onload = newonload;
-} else {
-    window.onload = loadUserComments;
-}
-*/
