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;
            long starttime = 0;
            if(timeout > 0)
                starttime = System.currentTimeMillis();
            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) {
                            lock.wait();
                        }
                        else {
                            long now = System.currentTimeMillis();
                            long elapsed = now-starttime; 
                            if(elapsed > timeout)   /* Give up on timeout */
                                return false;
                            lock.wait(timeout-elapsed);
                        }
                    } 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;
                }
            }
        }
    }
}

@@ -19,16 +19,54 @@
 .utils;%0A
+import java.io.ByteArrayOutputStream;%0A
 import j
@@ -108,32 +108,98 @@
 FoundException;%0A
+import java.io.FileOutputStream;%0Aimport java.io.RandomAccessFile;%0A
 import java.util
@@ -327,16 +327,47 @@
 ap.Log;%0A
+import org.dynmap.debug.Debug;%0A
 /**%0A * I
@@ -5089,16 +5089,150 @@
 ES = 6;%0A
+    %0A    private static ByteArrayOutputStream baos = new ByteArrayOutputStream();%0A    private static Object baos_lock = new Object();%0A
     /**%0A
@@ -5477,28 +5477,51 @@
     
-%0A        while(!done
+byte%5B%5D rslt;%0A        synchronized(baos_lock
 ) %7B%0A
@@ -5528,34 +5528,38 @@
             
-try %7B%0A    
+baos.reset();%0A
             
@@ -5583,21 +5583,295 @@
 , type, 
-fname
+baos); /* Write to byte array stream - prevent bogus I/O errors */%0A            rslt = baos.toByteArray();%0A        %7D%0A        while(!done) %7B%0A            RandomAccessFile f = null;%0A            try %7B%0A                f = new RandomAccessFile(fname, %22rw%22);%0A                f.write(rslt
 );%0A     
@@ -6002,32 +6002,35 @@
             
-Log.info
+Debug.debug
 (%22Image file
@@ -6412,32 +6412,172 @@
               %7D%0A
+            %7D finally %7B%0A                if(f != null) %7B%0A                    try %7B f.close(); %7D catch (IOException iox) %7B%7D%0A                %7D%0A
             %7D%0A  

Modify ImageIO use to avoid re-encoding on retry, do better buffering