/*
* $Id$
*
* Copyright (c) 2007 by Joel Uckelman
*
* 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
* @deprecated Create temporary files with {@link File.createTempFile()}
* in {@link VASSAL.Info.getTempDir()}.
*/
@Deprecated
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);
}
}