I'm building an application that serves as a web-based file editor. Users can select a file they want to edit, then that file is retrieved from the server and shown in a field. The user can then edit the file, and eventually save it.
This introduces a race-condition I'm trying to solve.
The client-side file is not always in sync with the server-side file. When a user opens a file that has already been opened and edited in another place, saving will overwrite any edits done by the other client. While this could be fixed using some form of merging, this is quite a bit outside the scope of this project.
When a user logs in on two different pc's or from two different browsers, and tries to open the same file twice, this can be detected using the session key. However, when a user opens a second tab, the session key will be identical.
Currently I periodically ping the file (say, every 2 seconds) and check if the previous ping is about 2 seconds ago. if it is less than that (minus some value accounting for lag) there might be two clients watching that file. This however breaks if a user quickly switches between a file and back, and is just plain ugly.
Is there a better, cleaner way of doing this?
I'm using a Django backend, and a frontend that relies heavily on jQuery, so any functionality based on these two would have my preference.
Some relevant code is shown below:
From the client-side, periodically dinging the file server-side:
setInterval(function(){
opened = $('input[name=file]:checked', '#files').val();
if(opened){
$.post('./' + opened + '/ding').error(function(){
alert('Something is awry.');
});
}
}, 2000);
From the server-side, handling these dings:
def ding(request, user_id, project_id, file_id):
user = User.objects.get(pk=user_id)
project = Project.objects.get(pk=project_id)
file = File.objects.get(pk=file_id)
session_key = request.session.session_key
can_claim = file.last_seen_open == None or timezone.now() - file.last_seen_open > datetime.timedelta(seconds=4)
is_mine = file.last_opened_by == session_key
is_iffy = file.last_seen_open != None and timezone.now() - file.last_seen_open < datetime.timedelta(seconds=1)
if is_iffy:
return HttpResponse(status=409, content="File is iffy")
if can_claim or is_mine:
file.last_opened_by = session_key
file.last_seen_open = timezone.now()
file.save()
return HttpResponse(status=200, content="File ding'd")
else:
return HttpResponse(status=409, content="File claimed by someone else")
The response "409: File is iffy" is returned if it seems that the file is opened twice on the same key