views:

436

answers:

4

Is there any way to lock a file until a browser is closed, the user leaves the current page, or their session expires?

I have a java app that reads annotations from a text file and lets a user modify or add more annotations to a pdf through the java app. Once they do, they click 'save' and the full annotation file is returned to it's original data file.

The issue is that 2 people can open the same annotation file and perform different updates. Then, when each saves, they overwrite the existing file so that only the 2nd user's changes are saved.

The ideal solution is to let 1 person 'check-out' the file for edit, make their modifications until either they close the window, navigate away from the page, or their session expires, then the file would automatically 'check-in'. Any way of doing this in C#? Thanks!

A: 

Well is there is no simple built in way to do this , what you can do is create a {username}.lock file in the same folder as the annotations folder. Then before you read or save check if there is a .lock file in the directory.

If there is a file tell the user its been locked and can not be updated. Obviously you need to make sure that you delete the .lock file otherwise after first access no one will ever be able to access the annotations file.

RC1140
That is my idea, but the issue there is deleting the file when their session expires, the browser is closed, or they navigate away from the page. They may perform multiple saves before leaving the page, so deleting per save won't really work. Or they may not save at all. Catching the browser close, when they leave the page, and when their session expires (in case they leave the tool open) is a must. That's where I'm stuck -- especially the session.
Zach
A not so elegant solution is to write the current date and time to the lock file at a regular interval , then check when someone else wants to access the file how long it has been since the current user has written to the file. If its longer than say 10 mins you can consider the user logged out and remove the lock.
RC1140
A: 

You could put your lock code in the Session_Start and your release code in the Session_End methods of the Global.asax file. However, trying to determine when the user has really left the session in order to trigger these events without waiting on the Session timeout is difficult to detect but it can be done via AJAX and some Javascript.

Example

protected void Application_Start()
{
    Application("AnnotationFileLocked") = False;
}
...

protected void Session_End()
{
    if ((bool)Application("AnnotationFileLocked"))
    {
         // Session("UserName") could be set on Login
         if ((string)Application("AnnotationFileLockedBy") == (string)Session("UserName"))
         {
             Application("AnnotationFileLocked") = False;
             Application.Remove("AnnotationFileLockedBy");
         }
    }
}

Then wherever in your code you check out the file:

public void CheckOutFile()
{
    if (!(bool)Application("AnnotationFileLocked"))
    {
        Application("AnnotationFileLocked") = True
        Application("AnnotationFileLockedBy") = (string)Session("UserName");
        // do stuff
    }
}
James
The issue there is that the app does many more things. The annotation files are in subdirectories (hundreds to thousands) so checking each sub-directory's lock may be a performance crush.
Zach
You could maintain a boolean value in the Application object which can be checked in the Session_Start method i.e. if (!(bool)Application("AnnotationFileLocked"))...So on start of the application this would be defaulted to False, when the first session locks it set it to true and set it to false on session end.
James
That actually looks like it would solve the issue quite easily with sessions. Can this be incorporated into onbeforeunload so that when they navigate away, we delete the lock?
Zach
Realistically what you would want to do is have an event handler which you can call via Ajax (from the client side) when the user leaves the page. So yes it can be done.
James
Also, there are a number of instances in which Session_End isn't going to fire: If you're not using InProc sessions (i.e. you're in a farm scenario), the server or worker process resets, etc
Zhaph - Ben Duguid
@Zhaph I suppose the consolation is the Session_End will always fire on the Timeout so if need by just reduce the timeout length
James
This seems to be working well James. Can you provide any links to the Ajax functionality you speak of that I can go on? I am very inexperienced with Ajax but could take a tutorial and work with it. Thanks again for everyone's help.
Zach
@Zach glad I could help. Here is a nice tutorial on ASP.NET callbacks and AJAX Patterns http://www.themastech.net/Tutorials/ClientCallbacks/Default.aspx
James
Great. I'll give this a shot. thanks again!
Zach
A: 

If two users being able to edit is the way the app works, does it not maybe make more sense to take a temporary copy of the file per user for editing?

Paddy
That's what the app does...it just pulls the annotation data and then loads it. So 2 people can access at the same time. The issue is if 2 people update annotations (move them, delete them, etc.) you still don't know which changes to accept and which not to...they could conflict greatly.
Zach
In that case would it not be a better option to prompt the user who is last to submit their changes that their changes are clashing and they need to resolve them?
James
That's not a bad idea, but I'm not sure that the complexity is something we have time for. I guess we could just pop that up, but having a conflict management tool would be too complex. I may look into this route further.
Zach
A: 

There is really no good way to do locks like that. Though you can put code into Session_End to clean up the locks, that event is not guaranteed to fire. For instance, the server might crash during a session. You'd be left with a permanently locked file.

Since you want simplicity, you might consider using timeout-based locking. When user A starts editing the file, they get exclusive access to it for 60 minutes (or whatever span you choose). You put an entry in your data store, saying the most recent edit lock is for user A and expires at 12:42pm. User A can edit as much as he likes in that time. If he's done earlier, he can click a "finished" button that removes the lock entry. You might even give him a "more time" button that updates the lock-expiry timestamp to 60 minutes from now.

If user B comes along and tries to edit the file, your app checks the list and sees that this file is locked until 12:42pm today. If user A goes away and never finishes, or saves and forgets to unlock, or if the app crashes, you don't need to worry about unlocking or cleaning up. User B just has to wait around until past 12:42, and then your app will ignore the existing lock entry because it's expired.

A lot of wikis use a method similar to this. It may not be perfect, but it's easy for users to understand, and doesn't require any operator maintenance.

Auraseer