views:

195

answers:

2

I am looking to have multiple users edit the same image using the gd library's imagecopymerge function.

I am worried that two users might select to edit the image at the same time. The application then merge's the two users images seperately and finally saves them but one overwrites the other and thus one of the users images is missing.

I am not sure of how to test the above... is it a possibility and if so how can I combat it?

+2  A: 

Store the last-modified timestamp when the user begins editing an image. when they submit their changes, check against the last-modified timestamp again. If it's different, inform the user than another user has already modified the image.

You might want to let the user see the new version of the image before choosing to overwrite. Also, you may want to provide a version control system (a la wikipedia) to let people roll back changes.

EDIT (In response to comments below)

  1. As opposed to storing the last modified time stamp (or perhaps in addition to), store a hash of the original image. and compare those immediately before overwriting. Otherwise same as above.

  2. The other option, as you suggested would also work well. at step 3 in your workflow create an [imagefilename].lock file (or modify a field in the DB if they are stored there) at the beginning of the process if one doesn't already exist, of course if one does exist, then same as above.

  3. To expand on the DB option in point 2 above, it would be much simpler to have a table like: image_id | image_data

    query including the md5 when you begin (optionally compute the md5 in php instead to reduce load on your db)

    select image_data, md5(image_data) from image_table where image_id=1
    

    Then when you want to write back, do

    update image_table set image_data=? where image_id=? and md5(image_data)=?
    

    This makes the update conditional on the md5 being identical before being overwritten, and it remains in a single, simple query.

    Then check the affected_rows. If no rows were affected, then check the hash. If the hash is different then presumably the query failed to update due to the md5 check. (Note: you will probably also want to check for any mysql errors)

Jonathan Fingland
Sorry I diddn't make myself clear - the editing is literally just adding a picture to a big picture so it should take a matter of milliseconds... as such I don't think the timestamp will be different. Ideally I would have some sort of transaction system.
Mark
@Mark: that's certainly an option. I think you might need to explain the work flow in a little more detail, though. Is it: 1) user selects big image to add to; 2) user selects picture to add to image; 3) user selects where to place the picture being added. And at the completion of step 3, the big image is modified with the change?
Jonathan Fingland
Sorry for being unclear. 1) users choose a large image 2) they upload their own image 3) (this is the risky part) the large image is opened and their image is inserted and the old large image is overwritten. My fear is that two users might reach step three at the same time. Thus meaning my application would open a version of the large image with neither of the users additions and then add the users image to their version of the large image - resulting in two different versions of the large image (one containing usr1's addition and the other usr2's) meaning the last to save overwrites the other
Mark
Thankyou for your input. Your idea with regards to checking the md5 in the database reduces the chances massively but is still not truely transactional as there is still the saving time of the image. However I like the idea about modifying a field in the db. I did not think about even using the db. Having considered it I can use an innodb table and start a transaction on the row related to the large image, then merge and save the image and then unlock it. Meaning it is truely transactional and requires no data to be changed in the db...just a row lock for milliseconds.
Mark
What do you think?
Mark
sounds like a good plan.
Jonathan Fingland
A: 

Ah, what you basically want is saving the delta with respect to the original images.

One way:

  1. A user Fred fetches the original source image I1 and edits it. Fred posts back the whole new image N1
  2. You compute the delta of the image D1, that is {N1 - O}. Instead of saving just overwriting N1, you retrieve the current image I2 from disk which might have been changed in the meantime (I1 doesn't need to equal I2) . You apply D1 to I1 and overwrite the image with the resulting image, which we call I3.
    Beware, you need to remember I1 for this to work in $_SESSION for example.
  3. Now an other Ria could edit the same image during the previous steps. For instance, shortly after Fred got I1, Ria got it too. Just before Ria has finished her work, Fred has had his delta saved on disk resulting in I3, like we saw. So what now? You blindly apply step 2. I will show what happens: User2 submits his work N2, the delta D2 is computed by comparing I1 with N2 (The session for Ria contains I1). The current image is retrieved from disk, which is I3 as you might correctly remember. This delta D2 is applied to I3 resulting in I4, which is saved to disk. Result is that Ria has overwrited I3, but only those parts she has actually edited.

This recipe will work, because file saving is an atomic operation in php.

To get this working:

  1. you need to ensure when a user A retrieves an image I to edit, I needs to be stored in A's session. Easy.
  2. You need to be able to compute a diff of the image, and apply this diff to an image. This should be possible with imagemagick's compare function for instance. Looking at the example of that url you'll see that in the diff white pixels are those that untouched.
Exception e