package org.dynmap.utils;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.HashMap;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;

import org.dynmap.Log;
/**
 * Implements soft-locks for prevent concurrency issues with file updates
 */
public class FileLockManager {
    private static Object lock = new Object();
    private static HashMap<String, Integer> filelocks = new HashMap<String, Integer>();
    private static final Integer WRITELOCK = new Integer(-1);
    /**
     * Get write lock on file - exclusive lock, no other writers or readers
     * @throws InterruptedException
     */
    public static boolean getWriteLock(File f) {
        String fn = f.getPath();
        synchronized(lock) {
            boolean got_lock = false;
            while(!got_lock) {
                Integer lockcnt = filelocks.get(fn);    /* Get lock count */
                if(lockcnt != null) {   /* If any locks, can't get write lock */
                    try {
                        lock.wait(); 
                    } catch (InterruptedException ix) {
                        Log.severe("getWriteLock(" + fn + ") interrupted");
                        return false;
                    }
                }
                else {
                    filelocks.put(fn, WRITELOCK);
                    got_lock = true;
                }
            }
        }
        //Log.info("getWriteLock(" + f + ")");
        return true;
    }
    /**
     * Release write lock
     */
    public static void releaseWriteLock(File f) {
        String fn = f.getPath();
        synchronized(lock) {
            Integer lockcnt = filelocks.get(fn);    /* Get lock count */
            if(lockcnt == null)
                Log.severe("releaseWriteLock(" + fn + ") on unlocked file");
            else if(lockcnt.equals(WRITELOCK)) {
                filelocks.remove(fn);   /* Remove lock */
                lock.notifyAll();   /* Wake up folks waiting for locks */
            }
            else
                Log.severe("releaseWriteLock(" + fn + ") on read-locked file");
        }
        //Log.info("releaseWriteLock(" + f + ")");
    }
    /**
     * Get read lock on file - multiple readers allowed, blocks writers
     */
    public static boolean getReadLock(File f) {
        return getReadLock(f, -1);
    }
    /**
     * Get read lock on file - multiple readers allowed, blocks writers - with timeout (msec)
     */
    public static boolean getReadLock(File f, long timeout) {
        String fn = f.getPath();
        synchronized(lock) {
            boolean got_lock = false;
            boolean first_wait = true;
            while(!got_lock) {
                Integer lockcnt = filelocks.get(fn);    /* Get lock count */
                if(lockcnt == null) {
                    filelocks.put(fn, Integer.valueOf(1));  /* First lock */
                    got_lock = true;
                }
                else if(!lockcnt.equals(WRITELOCK)) {   /* Other read locks */
                    filelocks.put(fn, Integer.valueOf(lockcnt+1));
                    got_lock = true;
                }
                else {  /* Write lock in place */
                    try {
                        if((timeout > 0) && (!first_wait)) {    /* We already waited */
                            return false;
                        }
                        if(timeout < 0)
                            lock.wait();
                        else
                            lock.wait(timeout);
                        first_wait = false;
                    } catch (InterruptedException ix) {
                        Log.severe("getReadLock(" + fn + ") interrupted");
                        return false;
                    }
                }
            }
        }        
        //Log.info("getReadLock(" + f + ")");
        return true;
    }
    /**
     * Release read lock
     */
    public static void releaseReadLock(File f) {
        String fn = f.getPath();
        synchronized(lock) {
            Integer lockcnt = filelocks.get(fn);    /* Get lock count */
            if(lockcnt == null)
                Log.severe("releaseReadLock(" + fn + ") on unlocked file");
            else if(lockcnt.equals(WRITELOCK))
                Log.severe("releaseReadLock(" + fn + ") on write-locked file");
            else if(lockcnt > 1) {
                filelocks.put(fn, Integer.valueOf(lockcnt-1));
            }
            else {
                filelocks.remove(fn);   /* Remove lock */
                lock.notifyAll();   /* Wake up folks waiting for locks */
            }
        }
        //Log.info("releaseReadLock(" + f + ")");
    }
    private static final int MAX_WRITE_RETRIES = 6;
    /**
     * Wrapper for IOImage.write - implements retries for busy files
     */
    public static void imageIOWrite(BufferedImage img, String type, File fname) throws IOException {
        int retrycnt = 0;
        boolean done = false;
        
        while(!done) {
            try {
                ImageIO.write(img, type, fname);
                done = true;
            } catch (IOException fnfx) {
                if(retrycnt < MAX_WRITE_RETRIES) {
                    Log.info("Image file " + fname.getPath() + " - unable to write - retry #" + retrycnt);
                    try { Thread.sleep(50 << retrycnt); } catch (InterruptedException ix) { throw fnfx; }
                    retrycnt++;
                }
                else {
                    Log.info("Image file " + fname.getPath() + " - unable to write - failed");
                    throw fnfx;
                }
            }
        }
    }
}

@@ -2659,33 +2659,110 @@
     
-boolean first_wait = true
+long starttime = 0;%0A            if(timeout %3E 0)%0A                starttime = System.currentTimeMillis()
 ;%0A  
@@ -3341,25 +3341,24 @@
      if(
-(
 timeout 
 %3E 0) && 
@@ -3353,59 +3353,81 @@
 out 
-%3E
+%3C
  0) 
-&& (!first_wait)) %7B    /* We already waited */
+%7B%0A                            lock.wait();%0A                        %7D
 %0A   
@@ -3447,33 +3447,22 @@
         
-    return false;
+else %7B
 %0A       
@@ -3482,90 +3482,186 @@
     
-%7D%0A                        if(timeout %3C 0)%0A                            lock.wait();
+    long now = System.currentTimeMillis();%0A                            long elapsed = now-starttime; %0A                            if(elapsed %3E timeout)   /* Give up on timeout */
 %0A   
@@ -3673,36 +3673,53 @@
                 
-e
+        return fa
 lse
+;
 %0A               
@@ -3748,16 +3748,24 @@
 (timeout
+-elapsed
 );%0A     
@@ -3787,27 +3787,9 @@
     
-first_wait = false;
+%7D
 %0A   

Fix premature timeouts on tile read waits, causing missed tile loads