Locking script when accessing spreadsheet doesnot work - concurrency

My problem is how to ensure that no data will be lost while concurrent access.
I have script published as web-app. I want to add new row to DATA_SHEET. The function that handles submit button looks like this:
function onButtonSubmit(e) {
var app = UiApp.getActiveApplication();
var lock = LockService.getPublicLock();
while (! lock.tryLock(1000))
;
var ssheet = SpreadsheetApp.openById(SHEET_ID);
var sheet = ssheet.getSheetByName(DATA_SHEET);
var lastRow = sheet.getLastRow();
var lastCol = sheet.getLastColumn();
var rangeToInsert = sheet.getRange(lastRow+1, 1, 1, lastCol);
var statText = rangeToInsert.getA1Notation();
rangeToInsert.setValues(<some data from webapp form>);
app.getElementById('statusLabel').setText(statText);
lock.releaseLock();
return app;
}
But it seems that this does not work. When I open two forms and click submit button within one second, it shows same range in statsLabel and writes data into same range. So I lose data from one form.
What is wrong with this code? It seems like tryLock() does not block script.
Is there any other way how to prevent concurrent write access to sheet?

It might be worth taking a look at appendRow(), rather than using getLastRow()/setValues() etc.
Allows for atomic appending of a row to a spreadsheet; can be used
safely even when multiple instances of the script are running at the
same time. Previously, one would have to call getLastRow(), then write
to that row. But if two invocations of the script were running at the
same time, they might both read the same value for getLastRow(), and
then overwrite each other's values.

while (! lock.tryLock(1000))
;
seems a bit hinky. Try this instead:
if (lock.tryLock(30000)) {
// I got the lock! Wo000t!!!11 Do whatever I was going to do!
} else {
// I couldn’t get the lock, now for plan B :(
GmailApp.sendEmail(“admin#example.com”, “epic fail”,
“lock acquisition fail!”);
}
http://googleappsdeveloper.blogspot.com/2011/10/concurrency-and-google-apps-script.html

You must insert this code when using getLastRow()/setValues() with lock.
SpreadsheetApp.flush();
// before
lock.releaseLock();

Related

How to increment a published var when accessed

I'm trying to setup some kind of global counter in my app.
What the best way of doing this?
I have the following code that works but it's damn ugly, I'd like to increment when the var is accessed so I don't have to call the function.
I tried to use get and set but it's not possible with property wrappers.
class GlobalDisplayInfo: ObservableObject {
#Published var nextAvailableInt : Int = 0
func getNextAvailableInt() -> Int {
nextAvailableInt += 1
return self.nextAvailableInt
}
}
What you want to achieve is not possible as it is against how Published works.
Let's say you access the published var in two places:
When the first one accesses the var, it would increment the var which would require the second place where it uses this var to refresh (access to this var again) which would require the var to increment again which would require the other place which uses this var to access and it just goes on - I think you got the point.
Your problem basically sounds rather like a design problem and I suggest you review it. Also, I think it'd be better if you just tell us what you want to achieve (without using any coding language, just a plain explanation of what you want) then an answer might come up.
It is not clear the usage, but looks like you need the following
class GlobalDisplayInfo: ObservableObject {
private var _nextAvailableInt = 0
var nextAvailableInt : Int {
_nextAvailableInt += 1
return _nextAvailableInt
}
}

SAPUI5 - Input error on growing list, logic issue

I am having an issue with a growing list. Previously I had a normal list, but as it is limited to displaying 100 items, I need to now change this to a growing list, which works fine now and I can get over 100 items loaded when I've put the growing="true" growingThreshold="50" growingScrollToLoad="false" properties on the list.
But now I have an issue with one of the number inputs in the custom list, when entering a number it is not staying set (it has a liveChange event that updates a text component).
I've set a breakpoint in the controller to test and it seems to bug out when I am trying to set the data changes (red arrow on attached image).
Can anyone see the issue with the logic? If any additional code snippets are required I could provide them.
onReceivedQuantityChange: function (oEvent) {
// get model and data
var oModel = this.getOrderModel();
var oData = oModel.getData();
// get item from path
var oItem = this._getOrderItemByPath(oEvent.getSource().getBindingContext(this.MODEL_ORDERS).getPath());
// set received value
oItem._ReceivedValue = oEvent.getParameters().newValue * (oItem.ValuationPrice / oItem.Quantity);
// apply data changes
oModel.setData(oData);
},
Controller code image
onReceivedQuantityChange: function (oEvent) {
var oModel = this.getOrderModel()
var sItemPath = oEvent.getSource().getBindingContext(this.MODEL_ORDERS).getPath()
var iValuationPrice = oModel.getProperty(sItemPath + '/ValuationPrice')
var iQuantity = oModel.getProperty(sItemPath + '/Quantity')
var iNewValue = oEvent.getParameters().newValue
var iReceivedValue = iNewValue * (iValuationPrice / iQuantity)
oModel.setProperty(sItemPath + '/_ReceivedValue', iReceivedValue)
}
If you use setProperty() on the Model you're only chaning the specific Property in DataModel and Sapui5 is able to proceed bindingchanges on this Property only (and not the whole model).
If you get the data out of the model by getData() you are only getting a reference to this Object. If you change something on this Object, you don't have to set it back by setData() (it is already there because you used the reference of this Object).
But Sapui5 need to know that there was a specific change in datamodel and this is done by using setProperty()

Trouble accessing child objects in Firebase

I'm trying to pull a list of connected users in Firebase to simply populate a select dropdown. My problem is that I can't seem to access the child objects properly.
Using connectedUsers.userName (see below code) works but only for my own user data, it doesn't pull anything else.
It seemed to me like changing "myUserRef.on" to "userListRef.on" and using something like "snapshot.child('userName').val()" should work but it just throws undefined. The same goes for "connectedUsers.child.userName", I'm sure I'm missing something simple here.
In the below code by changing to "userListRef.on('child_added', function(snapshot)" I can successfully add and remove user data from Firebase, and log all of the objects to the console and all data looks fine when I drill down the objects. I just need a way to access that data so I can put all connected users into a select dropdown or remove them from it when they disconnect.
var userListRef = new Firebase('https://myaccount.firebaseIO.com/users/');
var myUserRef = userListRef.push();
// ADD USER DATA TO FIREBASE
var userId = $('#myIdInput').val();
var userName = $('#nameInput').val();
myUserRef.push({userId: userId, userName: userName});
// READ USER OBJECTS AND FIRE ADDUSER FUNCTION
myUserRef.on('child_added', function(snapshot) {
var connectedUsers = snapshot.val();
console.log(addUser);
//addUser(connectedUsers.userId, connectedUsers.userName);
});
// ADD USER TO SELECT DROPDOWN
function addUser(userId, userName) {
var modSelect = $('#tsmmodsendto');
modSelect.append($('<option></option>').attr("value", userId).text(userName));
}
// READ USER OBJECTS AND FIRE REMOVEUSER FUNCTION
myUserRef.on('child_removed', function(snapshot) {
var connectedUsers = snapshot.val();
console.log(removeUser);
//removeUser(connectedUsers.userId, connectedUsers.userName);
});
// REMOVE USER TO SELECT DROPDOWN
function removeUser(userId, userName) {
var modSelect = $('#tsmmodsendto');
modSelect.append($('<option></option>').removeAttr("value", userId).text(userName));
}
// ON DISCONNECT REMOVE USER DATA FROM FIREBASE
myUserRef.onDisconnect().remove();
The code you've written won't work properly because each user is writing to:
/users/PUSH_ID/PUSH_ID
And is then listening at:
/users/PUSH_ID
So every client is going to be listening only to its own data and will never see anyone else's data. In order to view other people's data, you need to all be listening / writing to the same path.
In your question you mention you see "undefined" if you change to listening at /users. Could you simplify your code, and use that approach, and perhaps then I can provide a more helpful answer?
Or if I'm not understanding correctly, please simplify and clarify your question.

If statement for cookie - WebMatrix/Razor

I have set a cookie that I want to use to populate a form, so that users don't need to keep filling out the same form (it's submitting an inquiry to owners of holiday villas).
I've got it working fine if the cookie is already set, but it errors out if there is no cookie set.
I'm guessing I'll need to use an "if" statement, but don't quite know how to write the code.
Here is the code that sets the cookie...
Response.Cookies["BookingEnquiry"]["ReqName"] = Request["BookingReqName"];
Response.Cookies["BookingEnquiry"]["ReqEmail"] = Request["BookingReqEmail"];
Response.Cookies["BookingEnquiry"]["ReqPhone"] = Request["BookingReqPhone"];
Response.Cookies["BookingEnquiry"]["NumAdults"] = Request["BookingNumAdults"];
Response.Cookies["BookingEnquiry"]["NumChildren"] = Request["BookingNumChildren"];
Response.Cookies["BookingEnquiry"]["ReqMessage"] = Request["BookingReqMessage"];
Response.Cookies["BookingEnquiry"].Expires = DateTime.Now.AddHours(4);
}
Here are the variables that collect info from the cookie...
var reqname = Request.Cookies["BookingEnquiry"]["ReqName"];
var reqemail = Request.Cookies["BookingEnquiry"]["ReqEmail"];
var reqphone = Request.Cookies["BookingEnquiry"]["ReqPhone"];
var numadults = Request.Cookies["BookingEnquiry"]["NumAdults"];
var numchildren = Request.Cookies["BookingEnquiry"]["NumChildren"];
var reqmessage = Request.Cookies["BookingEnquiry"]["ReqMessage"];
and here is a sample input from the form...
<label>Name</label>
<input type="text" name="BookingReqName" id="BookingReqName" placeholder="full name…" value="#reqname">
In WebMatrix C#.net, I think you are looking for something like this:
if(Request["BookingReqName"] != null)
{
Response.Cookies["BookingEnquiry"]["ReqName"] = Request["BookingReqName"];
}
else
{
Response.Cookies["BookingReqName"] = ""; //<--Whatever default value you want (I've used an empty string here, so you, at least, won't get a null reference error).
}
Or you can use the same code as a one liner (to not clutter up your code, however this will decrease readability, obv.).
if(Request["BookingReqName"] != null){Response.Cookies["BookingEnquiry"]["ReqName"] = Request["BookingReqName"];}else{Response.Cookies["BookingReqName"] = ""; //<--Whatever default value you want (I've used an empty string here, so you, at least, won't get a null reference error).}
You'll just have to do that for all of your lines requesting cookie values.
The point is, though, that anything can go in the "else" block that helps you handle what to do when the cookie values have been cleared/expired (which you must always expect). You could redirect to a page that requests information from the user to reset any "forgotten" configurations, or, if you want to persist the data no matter what, consider storing these values in a database, instead, as those values won't clear/expire.
One last thing, if this doesn't help:
If you find yourself wondering what value to store in the cookie (the default value you wish to specify), because you need to know, right then and there, what it was supposed to have remembered, then I am afraid it is time to reconsider how you have structured the flow of data.
Sorry, but I have done that, once upon a time, only with Session variables, and it wasn't pretty :)
If you need any help with the best way(s) to transfer data between web pages, check this very helpful, concise link from Mike Brind's website: http://www.mikesdotnetting.com/Article/192/Transferring-Data-Between-ASP.NET-Web-Pages
It should just be the following
if(Request.Cookies["BookingEnquiry"] == null)
{
return; // <- if BookingEnquiry is null we end this routine
}
// Normal code flow here...
or something similar

Accessing Item Fields via Sitecore Web Service

I am creating items on the fly via Sitecore Web Service. So far I can create the items from this function:
AddFromTemplate
And I also tried this link: http://blog.hansmelis.be/2012/05/29/sitecore-web-service-pitfalls/
But I am finding it hard to access the fields. So far here is my code:
public void CreateItemInSitecore(string getDayGuid, Oracle.DataAccess.Client.OracleDataReader reader)
{
if (getDayGuid != null)
{
var sitecoreService = new EverBankCMS.VisualSitecoreService();
var addItem = sitecoreService.AddFromTemplate(getDayGuid, templateIdRTT, "Testing", database, myCred);
var getChildren = sitecoreService.GetChildren(getDayGuid, database, myCred);
for (int i = 0; i < getChildren.ChildNodes.Count; i++)
{
if (getChildren.ChildNodes[i].InnerText.ToString() == "Testing")
{
var getItem = sitecoreService.GetItemFields(getChildren.ChildNodes[i].Attributes[0].Value, "en", "1", true, database, myCred);
string p = getChildren.ChildNodes[i].Attributes[0].Value;
}
}
}
}
So as you can see I am creating an Item and I want to access the Fields for that item.
I thought that GetItemFields will give me some value, but finding it hard to get it. Any clue?
My advice would be to not use the VSS (Visual Sitecore Service), but write your own service specifically for the thing you want it to do.
This way is usually more efficient because you can do exactly the thing you want, directly inside the service, instead of making a lot of calls to the VSS and handle your logic on the clientside.
For me, this has always been a better solution than using the VSS.
I am assuming you are looking to find out what the fields looks like and what the field IDs are.
You can call GetXml with the ID, it returns the item and all the versions and fields set in it, it won't show fields you haven't set.