VASSAL.tools.io.TempFileManager.java Source code

Java tutorial

Introduction

Here is the source code for VASSAL.tools.io.TempFileManager.java

Source

/*
 * $Id$
 *
 * Copyright (c) 2007 by Joel Uckelman
 * Copyright (c) 2013 by Marc Pawlowsky
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License (LGPL) as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, copies are available
 * at http://www.opensource.org.
 */

package VASSAL.tools.io;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.SystemUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import VASSAL.Info;

/**
 * Handles temporary files. <code>TempFileManager</code> cleans up
 * stale temporary files when the singleton is constructed, and ones
 * created by the current session on exit through a shutdown hook.
 *
 * <p>A temporary director is created in <code>"user.dir" + "/tmp"</code>
 * for each <code>TempFileManager</code> instance, which, since it is
 * a singleton, amounts to one temporary directory per VASSAL instance.
 * Each session directory has a corresponding lock file, which indicates
 * that the VASSAL instance associated with that directory is live.
 * Directories which are not live will be cleaned up on creation of a
 * <code>TempFileManager</code> instance.</p>
 *
 * <p>Due to Sun Bugs
 * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4171239">
 * #4171239</a>,
 * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4724038">
 * #4724038</a>, and
 * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6359560">
 * #6359560</a>, {@link File.deleteOnExit()} is unreliable on Windows,
 * and there is no way to unmap memory-mapped files so that they may
 * be deleted by {@link File.delete()}. This class overcomes these
 * shortcomings by handling temporary files directly.</p>
 *
 * @since 3.1.0
 * @author Joel Uckelman
 */
public class TempFileManager {
    private static final Logger logger = LoggerFactory.getLogger(TempFileManager.class);

    private final File tmpRoot;
    private File sessionRoot;
    private File lock;

    private static final String DIR_PREFIX = "vassal-";

    private TempFileManager() {
        //
        // set up for cleanup on shutdown
        //
        if (SystemUtils.IS_OS_WINDOWS) {
            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    // Run the garbage collector and finalize repeatedly, with
                    // exponentially increasing pauses, until we succeed at deleting
                    // the whole session temp directory or we hit the sleep limit.
                    long sleep = 1;
                    final long maxsleep = 1024;
                    while (true) {
                        System.gc();
                        System.runFinalization();

                        try {
                            cleanupSessionRoot();
                            break;
                        } catch (IOException e) {
                            if (sleep > maxsleep) {
                                // just log, since shutdown hooks don't have long to run
                                logger.error("", e);
                                break;
                            }

                            try {
                                Thread.sleep(sleep);
                            } catch (InterruptedException ignore) {
                            }

                            sleep *= 2;
                        }
                    }
                }
            });
        } else {
            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    try {
                        cleanupSessionRoot();
                    } catch (IOException e) {
                        // just log, since shutdown hooks don't have long to run
                        logger.error("", e);
                    }
                }
            });
        }

        tmpRoot = Info.getTempDir();

        //
        // clean up stale temporary files
        //
        if (tmpRoot.exists() && tmpRoot.isDirectory()) {
            final FileFilter filter = new FileFilter() {
                public boolean accept(File f) {
                    return f.isDirectory() && f.getName().startsWith(DIR_PREFIX);
                }
            };

            for (File f : tmpRoot.listFiles(filter)) {
                final File lock = new File(f.getParent(), f.getName() + ".lck");
                if (!lock.exists()) {
                    try {
                        FileUtils.forceDelete(f);
                    } catch (IOException e) {
                        logger.error("", e);
                    }
                }
            }
        }
    }

    private void cleanupSessionRoot() throws IOException {
        if (lock.exists())
            FileUtils.forceDelete(lock);
        FileUtils.forceDelete(sessionRoot);
    }

    private static final TempFileManager instance = new TempFileManager();

    /**
     * Returns the singleton for this class.
     *
     * @return an instance of this class
     */
    public static TempFileManager getInstance() {
        return instance;
    }

    public File getSessionRoot() throws IOException {
        if (sessionRoot == null)
            sessionRoot = createSessionRoot();
        return new File(sessionRoot.toString());
    }

    private File createSessionRoot() throws IOException {
        // ensure that we have a good temporary root
        if (!tmpRoot.exists() || (!tmpRoot.isDirectory() && !tmpRoot.delete())) {
            FileUtils.forceMkdir(tmpRoot);
        }

        // get the name for our session root
        final File dir = File.createTempFile(DIR_PREFIX, "", tmpRoot);
        // delete it in case a file was created
        dir.delete();

        // create our lock file before creating the directory to prevent
        // a race with another instance of VASSAL
        lock = new File(tmpRoot, dir.getName() + ".lck");
        lock.createNewFile();
        lock.deleteOnExit();

        // now create our session root directory
        FileUtils.forceMkdir(dir);

        return dir;
    }

    /**
     * Creates a new directory with the given name in the session temporary
     * directory.
     *
     * @param dirname the name of the directory to create
     * @throws IOException if the directory cannot be created.
     */
    public File createTempDir(String dirname) throws IOException {
        if (sessionRoot == null)
            sessionRoot = createSessionRoot();

        final File dir = new File(sessionRoot, dirname);
        FileUtils.forceMkdir(dir);
        return dir;
    }

    /**
     * Creates a new empty file in the session temporary directory, using the
     * given prefix and suffix strings to generate its name. This method
     * is otherwise the same as {@link File.createTempFile(String,String)}.
     *
     * @param prefix
     * @param suffix
     * @return the created file
     * @throws IOException if a file could not be created
     */
    public File createTempFile(String prefix, String suffix) throws IOException {
        if (sessionRoot == null)
            sessionRoot = createSessionRoot();
        return File.createTempFile(prefix, suffix, sessionRoot);
    }
}