diff --git a/src/java/com/threerings/cast/bundle/BundleUtil.java b/src/java/com/threerings/cast/bundle/BundleUtil.java
index caa78d1e..3d8ef106 100644
--- a/src/java/com/threerings/cast/bundle/BundleUtil.java
+++ b/src/java/com/threerings/cast/bundle/BundleUtil.java
@@ -29,27 +29,24 @@ import java.io.ObjectInputStream;
import com.samskivert.io.StreamUtil;
import com.threerings.cast.Log;
+import com.threerings.resource.FileResourceBundle;
import com.threerings.resource.ResourceBundle;
/**
- * Utility functions related to creating and manipulating component
- * bundles.
+ * Utility functions related to creating and manipulating component bundles.
*/
public class BundleUtil
{
/** The path in the metadata bundle to the serialized action table. */
public static final String ACTIONS_PATH = "actions.dat";
- /** The path in the metadata bundle to the serialized action tile sets
- * table. */
+ /** The path in the metadata bundle to the serialized action tile sets table. */
public static final String ACTION_SETS_PATH = "action_sets.dat";
- /** The path in the metadata bundle to the serialized component class
- * table. */
+ /** The path in the metadata bundle to the serialized component class table. */
public static final String CLASSES_PATH = "classes.dat";
- /** The path in the component bundle to the serialized component id to
- * class/type mapping. */
+ /** The path in the component bundle to the serialized component id to class/type mapping. */
public static final String COMPONENTS_PATH = "components.dat";
/** The file extension of our action tile images. */
@@ -59,22 +56,19 @@ public class BundleUtil
public static final String TILESET_EXTENSION = ".dat";
/**
- * Attempts to load an object from the supplied resource bundle with
- * the specified path.
+ * Attempts to load an object from the supplied resource bundle with the specified path.
*
- * @param wipeBundleOnFailure if there is an error reading the object
- * from the bundle and this parameter is true, we will instruct the
- * bundle to delete its underlying jar file before propagating the
- * exception with the expectation that it will be redownloaded and
- * repaired the next time the application is run.
+ * @param wipeOnFailure if there is an error reading the object from the bundle and this
+ * parameter is true, we will instruct the bundle to delete its underlying jar file before
+ * propagating the exception with the expectation that it will be redownloaded and repaired the
+ * next time the application is run.
*
* @return the unserialized object in question.
*
- * @exception IOException thrown if an I/O error occurs while reading
- * the object from the bundle.
+ * @exception IOException thrown if an I/O error occurs while reading the object from the
+ * bundle.
*/
- public static Object loadObject (ResourceBundle bundle, String path,
- boolean wipeBundleOnFailure)
+ public static Object loadObject (ResourceBundle bundle, String path, boolean wipeOnFailure)
throws IOException, ClassNotFoundException
{
InputStream bin = null;
@@ -86,20 +80,19 @@ public class BundleUtil
return new ObjectInputStream(bin).readObject();
} catch (InvalidClassException ice) {
- Log.warning("Aiya! Serialized object is hosed " +
- "[bundle=" + bundle.getSource().getPath() +
- ", element=" + path +
- ", error=" + ice.getMessage() + "].");
+ Log.warning("Aiya! Serialized object is hosed [bundle=" + bundle +
+ ", element=" + path + ", error=" + ice.getMessage() + "].");
return null;
} catch (IOException ioe) {
- Log.warning("Error reading resource from bundle " +
- "[bundle=" + bundle + ", path=" + path +
- ", wiping?=" + wipeBundleOnFailure + "].");
- if (wipeBundleOnFailure) {
+ Log.warning("Error reading resource from bundle [bundle=" + bundle + ", path=" + path +
+ ", wiping?=" + wipeOnFailure + "].");
+ if (wipeOnFailure) {
StreamUtil.close(bin);
bin = null;
- bundle.wipeBundle(false);
+ if (bundle instanceof FileResourceBundle) {
+ ((FileResourceBundle)bundle).wipeBundle(false);
+ }
}
throw ioe;
diff --git a/src/java/com/threerings/cast/bundle/BundledComponentRepository.java b/src/java/com/threerings/cast/bundle/BundledComponentRepository.java
index 7f3d6b62..1d340eb4 100644
--- a/src/java/com/threerings/cast/bundle/BundledComponentRepository.java
+++ b/src/java/com/threerings/cast/bundle/BundledComponentRepository.java
@@ -36,6 +36,7 @@ import com.samskivert.util.IntIntMap;
import com.samskivert.util.Predicate;
import com.samskivert.util.Tuple;
+import com.threerings.resource.FileResourceBundle;
import com.threerings.resource.ResourceBundle;
import com.threerings.resource.ResourceManager;
@@ -270,7 +271,7 @@ public class BundledComponentRepository
// from interface ImageDataProvider
public String getIdent ()
{
- return "bcr:" + _bundle.getSource();
+ return "bcr:" + _bundle.getIdent();
}
// from interface ImageDataProvider
@@ -331,8 +332,8 @@ public class BundledComponentRepository
!StandardActions.SHADOW_TYPE.equals(type)) {
Log.warning("Unable to locate tileset for action '" + imgpath + "' " +
component + ".");
- if (_wipeOnFailure) {
- _bundle.wipeBundle(false);
+ if (_wipeOnFailure && _bundle instanceof FileResourceBundle) {
+ ((FileResourceBundle)_bundle).wipeBundle(false);
}
}
return null;
diff --git a/src/java/com/threerings/cast/bundle/tools/DumpBundle.java b/src/java/com/threerings/cast/bundle/tools/DumpBundle.java
index d745539f..4bbede1a 100644
--- a/src/java/com/threerings/cast/bundle/tools/DumpBundle.java
+++ b/src/java/com/threerings/cast/bundle/tools/DumpBundle.java
@@ -29,8 +29,9 @@ import java.util.Map;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.StringUtil;
-import com.threerings.resource.ResourceBundle;
import com.threerings.cast.bundle.BundleUtil;
+import com.threerings.resource.FileResourceBundle;
+import com.threerings.resource.ResourceBundle;
/**
* Dumps the contents of a component bundle to stdout.
@@ -48,7 +49,7 @@ public class DumpBundle
for (int i = 0; i < args.length; i++) {
File file = new File(args[i]);
try {
- ResourceBundle bundle = new ResourceBundle(file);
+ ResourceBundle bundle = new FileResourceBundle(file);
HashMap actions = (HashMap)BundleUtil.loadObject(
bundle, BundleUtil.ACTIONS_PATH, false);
diff --git a/src/java/com/threerings/media/tile/bundle/TileSetBundle.java b/src/java/com/threerings/media/tile/bundle/TileSetBundle.java
index 8bb0f9af..9493c7c6 100644
--- a/src/java/com/threerings/media/tile/bundle/TileSetBundle.java
+++ b/src/java/com/threerings/media/tile/bundle/TileSetBundle.java
@@ -22,7 +22,7 @@
package com.threerings.media.tile.bundle;
import java.awt.image.BufferedImage;
-import java.io.File;
+
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
@@ -36,30 +36,21 @@ import com.threerings.media.image.ImageDataProvider;
import com.threerings.media.tile.TileSet;
/**
- * A tileset bundle is used to load up tilesets by id from a persistent
- * bundle of tilesets stored on the local filesystem.
+ * A tileset bundle is used to load up tilesets by id from a persistent bundle of tilesets stored
+ * on the local filesystem.
*/
public class TileSetBundle extends HashIntMap
implements Serializable, ImageDataProvider
{
/**
- * Initializes this resource bundle with a reference to the jarfile
- * from which it was loaded and from which it can load image data. The
- * image manager will be used to decode the images.
+ * Initializes this resource bundle with a reference to the jarfile from which it was loaded
+ * and from which it can load image data. The image manager will be used to decode the images.
*/
public void init (ResourceBundle bundle)
{
_bundle = bundle;
}
- /**
- * Returns the bundle file from which our tiles are fetched.
- */
- public File getSource ()
- {
- return _bundle.getSource();
- }
-
/**
* Adds a tileset to this tileset bundle.
*/
@@ -95,7 +86,7 @@ public class TileSetBundle extends HashIntMap
// documentation inherited from interface
public String getIdent ()
{
- return "tsb:" + _bundle.getSource();
+ return "tsb:" + _bundle.getIdent();
}
// documentation inherited from interface
@@ -135,7 +126,7 @@ public class TileSetBundle extends HashIntMap
/** That from which we load our tile images. */
protected transient ResourceBundle _bundle;
- /** Increase this value when object's serialized state is impacted by
- * a class change (modification of fields, inheritance). */
+ /** Increase this value when object's serialized state is impacted by a class change
+ * (modification of fields, inheritance). */
private static final long serialVersionUID = 2;
}
diff --git a/src/java/com/threerings/media/tile/bundle/tools/DumpBundle.java b/src/java/com/threerings/media/tile/bundle/tools/DumpBundle.java
index 661febf2..a88ff2cc 100644
--- a/src/java/com/threerings/media/tile/bundle/tools/DumpBundle.java
+++ b/src/java/com/threerings/media/tile/bundle/tools/DumpBundle.java
@@ -24,6 +24,7 @@ package com.threerings.media.tile.bundle.tools;
import java.io.File;
import java.util.Iterator;
+import com.threerings.resource.FileResourceBundle;
import com.threerings.resource.ResourceManager;
import com.threerings.resource.ResourceBundle;
@@ -63,7 +64,7 @@ public class DumpBundle
try {
TileSetBundle tsb = null;
if (args[i].endsWith(".jar")) {
- ResourceBundle bundle = new ResourceBundle(file);
+ ResourceBundle bundle = new FileResourceBundle(file);
tsb = BundleUtil.extractBundle(bundle);
tsb.init(bundle);
} else {
diff --git a/src/java/com/threerings/resource/FileResourceBundle.java b/src/java/com/threerings/resource/FileResourceBundle.java
new file mode 100644
index 00000000..88c1d3ee
--- /dev/null
+++ b/src/java/com/threerings/resource/FileResourceBundle.java
@@ -0,0 +1,422 @@
+//
+// $Id: ResourceBundle.java 310 2007-10-26 19:50:53Z mdb $
+//
+// Nenya library - tools for developing networked games
+// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
+// http://www.threerings.net/code/nenya/
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published
+// by the Free Software Foundation; either version 2.1 of the License, or
+// (at your option) any later version.
+//
+// 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package com.threerings.resource;
+
+import java.awt.image.BufferedImage;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import com.samskivert.util.FileUtil;
+import com.samskivert.util.StringUtil;
+
+import org.apache.commons.io.IOUtils;
+
+/**
+ * A resource bundle provides access to the resources in a jar file.
+ */
+public class FileResourceBundle extends ResourceBundle
+{
+ /**
+ * Constructs a resource bundle with the supplied jar file.
+ *
+ * @param source a file object that references our source jar file.
+ */
+ public FileResourceBundle (File source)
+ {
+ this(source, false, false);
+ }
+
+ /**
+ * Constructs a resource bundle with the supplied jar file.
+ *
+ * @param source a file object that references our source jar file.
+ * @param delay if true, the bundle will wait until someone calls
+ * {@link #sourceIsReady} before allowing access to its resources.
+ * @param unpack if true the bundle will unpack itself into a
+ * temporary directory
+ */
+ public FileResourceBundle (File source, boolean delay, boolean unpack)
+ {
+ _source = source;
+ if (unpack) {
+ String root = stripSuffix(source.getPath());
+ _unpacked = new File(root + ".stamp");
+ _cache = new File(root);
+ }
+
+ if (!delay) {
+ sourceIsReady();
+ }
+ }
+
+ @Override // from ResourceBundle
+ public String getIdent ()
+ {
+ return _source.getPath();
+ }
+
+ @Override // from ResourceBundle
+ public InputStream getResource (String path)
+ throws IOException
+ {
+ // unpack our resources into a temp directory so that we can load
+ // them quickly and the file system can cache them sensibly
+ File rfile = getResourceFile(path);
+ return (rfile == null) ? null : new FileInputStream(rfile);
+ }
+
+ @Override // from ResourceBundle
+ public BufferedImage getImageResource (String path)
+ throws IOException
+ {
+ return ResourceManager.loadImage(getResourceFile(path));
+ }
+
+ /**
+ * Returns the {@link File} from which resources are fetched for this
+ * bundle.
+ */
+ public File getSource ()
+ {
+ return _source;
+ }
+
+ /**
+ * @return true if the bundle is fully downloaded and successfully
+ * unpacked.
+ */
+ public boolean isUnpacked ()
+ {
+ return (_source.exists() && _unpacked != null &&
+ _unpacked.lastModified() == _source.lastModified());
+ }
+
+ /**
+ * Called by the resource manager once it has ensured that our
+ * resource jar file is up to date and ready for reading.
+ *
+ * @return true if we successfully unpacked our resources, false if we
+ * encountered errors in doing so.
+ */
+ public boolean sourceIsReady ()
+ {
+ // make a note of our source's last modification time
+ _sourceLastMod = _source.lastModified();
+
+ // if we are unpacking files, the time to do so is now
+ if (_unpacked != null && _unpacked.lastModified() != _sourceLastMod) {
+ try {
+ resolveJarFile();
+ } catch (IOException ioe) {
+ Log.warning("Failure resolving jar file '" + _source +
+ "': " + ioe + ".");
+ wipeBundle(true);
+ return false;
+ }
+
+ Log.info("Unpacking into " + _cache + "...");
+ if (!_cache.exists()) {
+ if (!_cache.mkdir()) {
+ Log.warning("Failed to create bundle cache directory '" +
+ _cache + "'.");
+ closeJar();
+ // we are hopelessly fucked
+ return false;
+ }
+ } else {
+ FileUtil.recursiveClean(_cache);
+ }
+
+ // unpack the jar file (this will close the jar when it's done)
+ if (!FileUtil.unpackJar(_jarSource, _cache)) {
+ // if something went awry, delete everything in the hopes
+ // that next time things will work
+ wipeBundle(true);
+ return false;
+ }
+
+ // if everything unpacked smoothly, create our unpack stamp
+ try {
+ _unpacked.createNewFile();
+ if (!_unpacked.setLastModified(_sourceLastMod)) {
+ Log.warning("Failed to set last mod on stamp file '" +
+ _unpacked + "'.");
+ }
+ } catch (IOException ioe) {
+ Log.warning("Failure creating stamp file '" + _unpacked +
+ "': " + ioe + ".");
+ // no need to stick a fork in things at this point
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Clears out everything associated with this resource bundle in the
+ * hopes that we can download it afresh and everything will work the
+ * next time around.
+ */
+ public void wipeBundle (boolean deleteJar)
+ {
+ // clear out our cache directory
+ if (_cache != null) {
+ FileUtil.recursiveClean(_cache);
+ }
+
+ // delete our unpack stamp file
+ if (_unpacked != null) {
+ _unpacked.delete();
+ }
+
+ // clear out any .jarv file that Getdown might be maintaining so
+ // that we ensure that it is revalidated
+ File vfile = new File(FileUtil.resuffix(_source, ".jar", ".jarv"));
+ if (vfile.exists() && !vfile.delete()) {
+ Log.warning("Failed to delete " + vfile + ".");
+ }
+
+ // close and delete our source jar file
+ if (deleteJar && _source != null) {
+ closeJar();
+ if (!_source.delete()) {
+ Log.warning("Failed to delete " + _source +
+ " [exists=" + _source.exists() + "].");
+ }
+ }
+ }
+
+ /**
+ * Returns a file from which the specified resource can be loaded.
+ * This method will unpack the resource into a temporary directory and
+ * return a reference to that file.
+ *
+ * @param path the path to the resource in this jar file.
+ *
+ * @return a file from which the resource can be loaded or null if no
+ * such resource exists.
+ */
+ public File getResourceFile (String path)
+ throws IOException
+ {
+ if (resolveJarFile()) {
+ return null;
+ }
+
+ // if we have been unpacked, return our unpacked file
+ if (_cache != null) {
+ File cfile = new File(_cache, path);
+ if (cfile.exists()) {
+ return cfile;
+ } else {
+ return null;
+ }
+ }
+
+ // otherwise, we unpack resources as needed into a temp directory
+ String tpath = StringUtil.md5hex(_source.getPath() + "%" + path);
+ File tfile = new File(getCacheDir(), tpath);
+ if (tfile.exists() && (tfile.lastModified() > _sourceLastMod)) {
+ return tfile;
+ }
+
+ JarEntry entry = _jarSource.getJarEntry(path);
+ if (entry == null) {
+// Log.info("Couldn't locate " + path + " in " + _jarSource + ".");
+ return null;
+ }
+
+ // copy the resource into the temporary file
+ BufferedOutputStream fout = new BufferedOutputStream(new FileOutputStream(tfile));
+ InputStream jin = _jarSource.getInputStream(entry);
+ IOUtils.copy(jin, fout);
+ jin.close();
+ fout.close();
+
+ return tfile;
+ }
+
+ /**
+ * Returns true if this resource bundle contains the resource with the
+ * specified path. This avoids actually loading the resource, in the
+ * event that the caller only cares to know that the resource exists.
+ */
+ public boolean containsResource (String path)
+ {
+ try {
+ if (resolveJarFile()) {
+ return false;
+ }
+ return (_jarSource.getJarEntry(path) != null);
+ } catch (IOException ioe) {
+ return false;
+ }
+ }
+
+ /**
+ * Returns a string representation of this resource bundle.
+ */
+ public String toString ()
+ {
+ try {
+ resolveJarFile();
+ return (_jarSource == null) ? "[file=" + _source + "]" :
+ "[path=" + _jarSource.getName() + "]";
+
+ } catch (IOException ioe) {
+ return "[file=" + _source + ", ioe=" + ioe + "]";
+ }
+ }
+
+ /**
+ * Creates the internal jar file reference if we've not already got
+ * it; we do this lazily so as to avoid any jar- or zip-file-related
+ * antics until and unless doing so is required, and because the
+ * resource manager would like to be able to create bundles before the
+ * associated files have been fully downloaded.
+ *
+ * @return true if the jar file could not yet be resolved because we
+ * haven't yet heard from the resource manager that it is ready for us
+ * to access, false if all is cool.
+ */
+ protected boolean resolveJarFile ()
+ throws IOException
+ {
+ // if we don't yet have our resource bundle's last mod time, we
+ // have not yet been notified that it is ready
+ if (_sourceLastMod == -1) {
+ return true;
+ }
+
+ if (!_source.exists()) {
+ throw new IOException("Missing jar file for resource bundle: " +
+ _source + ".");
+ }
+
+ try {
+ if (_jarSource == null) {
+ _jarSource = new JarFile(_source);
+ }
+ return false;
+
+ } catch (IOException ioe) {
+ String msg = "Failed to resolve resource bundle jar file '" +
+ _source + "'";
+ Log.warning(msg + ".");
+ Log.logStackTrace(ioe);
+ throw (IOException) new IOException(msg).initCause(ioe);
+ }
+ }
+
+ /**
+ * Closes our (possibly opened) jar file.
+ */
+ protected void closeJar ()
+ {
+ try {
+ if (_jarSource != null) {
+ _jarSource.close();
+ }
+ } catch (Exception ioe) {
+ Log.warning("Failed to close jar file [path=" + _source +
+ ", error=" + ioe + "].");
+ }
+ }
+
+ /**
+ * Returns the cache directory used for unpacked resources.
+ */
+ public static File getCacheDir ()
+ {
+ if (_tmpdir == null) {
+ String tmpdir = System.getProperty("java.io.tmpdir");
+ if (tmpdir == null) {
+ Log.info("No system defined temp directory. Faking it.");
+ tmpdir = System.getProperty("user.home");
+ }
+ setCacheDir(new File(tmpdir));
+ }
+ return _tmpdir;
+ }
+
+ /**
+ * Specifies the directory in which our temporary resource files should be stored.
+ */
+ public static void setCacheDir (File tmpdir)
+ {
+ String rando = Long.toHexString((long)(Math.random() * Long.MAX_VALUE));
+ _tmpdir = new File(tmpdir, "narcache_" + rando);
+ if (!_tmpdir.exists()) {
+ if (_tmpdir.mkdirs()) {
+ Log.debug("Created narya temp cache directory '" + _tmpdir + "'.");
+ } else {
+ Log.warning("Failed to create temp cache directory '" + _tmpdir + "'.");
+ }
+ }
+
+ // add a hook to blow away the temp directory when we exit
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ public void run () {
+ Log.info("Clearing narya temp cache '" + _tmpdir + "'.");
+ FileUtil.recursiveDelete(_tmpdir);
+ }
+ });
+ }
+
+ /** Strips the .jar off of jar file paths. */
+ protected static String stripSuffix (String path)
+ {
+ if (path.endsWith(".jar")) {
+ return path.substring(0, path.length()-4);
+ } else {
+ // we have to change the path somehow
+ return path + "-cache";
+ }
+ }
+
+ /** The file from which we construct our jar file. */
+ protected File _source;
+
+ /** The last modified time of our source jar file. */
+ protected long _sourceLastMod = -1;
+
+ /** A file whose timestamp indicates whether or not our existing jar
+ * file has been unpacked. */
+ protected File _unpacked;
+
+ /** A directory into which we unpack files from our bundle. */
+ protected File _cache;
+
+ /** The jar file from which we load resources. */
+ protected JarFile _jarSource;
+
+ /** A directory in which we temporarily unpack our resource files. */
+ protected static File _tmpdir;
+}
diff --git a/src/java/com/threerings/resource/ResourceBundle.java b/src/java/com/threerings/resource/ResourceBundle.java
index a960c164..f163e6b3 100644
--- a/src/java/com/threerings/resource/ResourceBundle.java
+++ b/src/java/com/threerings/resource/ResourceBundle.java
@@ -21,411 +21,39 @@
package com.threerings.resource;
-import java.awt.image.BufferedImage;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-
-import com.samskivert.util.FileUtil;
-import com.samskivert.util.StringUtil;
-
-import org.apache.commons.io.IOUtils;
+import java.awt.image.BufferedImage;
/**
- * A resource bundle provides access to the resources in a jar file.
+ * A resource bundle provides access to media resources.
*/
-public class ResourceBundle
+public abstract class ResourceBundle
{
/**
- * Constructs a resource bundle with the supplied jar file.
- *
- * @param source a file object that references our source jar file.
+ * Uniquely identifies this resource bundle. Used to create cache keys.
*/
- public ResourceBundle (File source)
- {
- this(source, false, false);
- }
+ public abstract String getIdent ();
/**
- * Constructs a resource bundle with the supplied jar file.
- *
- * @param source a file object that references our source jar file.
- * @param delay if true, the bundle will wait until someone calls
- * {@link #sourceIsReady} before allowing access to its resources.
- * @param unpack if true the bundle will unpack itself into a
- * temporary directory
- */
- public ResourceBundle (File source, boolean delay, boolean unpack)
- {
- _source = source;
- if (unpack) {
- String root = stripSuffix(source.getPath());
- _unpacked = new File(root + ".stamp");
- _cache = new File(root);
- }
-
- if (!delay) {
- sourceIsReady();
- }
- }
-
- /**
- * Returns the {@link File} from which resources are fetched for this
- * bundle.
- */
- public File getSource ()
- {
- return _source;
- }
-
- /**
- * @return true if the bundle is fully downloaded and successfully
- * unpacked.
- */
- public boolean isUnpacked ()
- {
- return (_source.exists() && _unpacked != null &&
- _unpacked.lastModified() == _source.lastModified());
- }
-
- /**
- * Called by the resource manager once it has ensured that our
- * resource jar file is up to date and ready for reading.
- *
- * @return true if we successfully unpacked our resources, false if we
- * encountered errors in doing so.
- */
- public boolean sourceIsReady ()
- {
- // make a note of our source's last modification time
- _sourceLastMod = _source.lastModified();
-
- // if we are unpacking files, the time to do so is now
- if (_unpacked != null && _unpacked.lastModified() != _sourceLastMod) {
- try {
- resolveJarFile();
- } catch (IOException ioe) {
- Log.warning("Failure resolving jar file '" + _source +
- "': " + ioe + ".");
- wipeBundle(true);
- return false;
- }
-
- Log.info("Unpacking into " + _cache + "...");
- if (!_cache.exists()) {
- if (!_cache.mkdir()) {
- Log.warning("Failed to create bundle cache directory '" +
- _cache + "'.");
- closeJar();
- // we are hopelessly fucked
- return false;
- }
- } else {
- FileUtil.recursiveClean(_cache);
- }
-
- // unpack the jar file (this will close the jar when it's done)
- if (!FileUtil.unpackJar(_jarSource, _cache)) {
- // if something went awry, delete everything in the hopes
- // that next time things will work
- wipeBundle(true);
- return false;
- }
-
- // if everything unpacked smoothly, create our unpack stamp
- try {
- _unpacked.createNewFile();
- if (!_unpacked.setLastModified(_sourceLastMod)) {
- Log.warning("Failed to set last mod on stamp file '" +
- _unpacked + "'.");
- }
- } catch (IOException ioe) {
- Log.warning("Failure creating stamp file '" + _unpacked +
- "': " + ioe + ".");
- // no need to stick a fork in things at this point
- }
- }
-
- return true;
- }
-
- /**
- * Clears out everything associated with this resource bundle in the
- * hopes that we can download it afresh and everything will work the
- * next time around.
- */
- public void wipeBundle (boolean deleteJar)
- {
- // clear out our cache directory
- if (_cache != null) {
- FileUtil.recursiveClean(_cache);
- }
-
- // delete our unpack stamp file
- if (_unpacked != null) {
- _unpacked.delete();
- }
-
- // clear out any .jarv file that Getdown might be maintaining so
- // that we ensure that it is revalidated
- File vfile = new File(FileUtil.resuffix(_source, ".jar", ".jarv"));
- if (vfile.exists() && !vfile.delete()) {
- Log.warning("Failed to delete " + vfile + ".");
- }
-
- // close and delete our source jar file
- if (deleteJar && _source != null) {
- closeJar();
- if (!_source.delete()) {
- Log.warning("Failed to delete " + _source +
- " [exists=" + _source.exists() + "].");
- }
- }
- }
-
- /**
- * Fetches the named resource from this bundle. The path should be
- * specified as a relative, platform independent path (forward
- * slashes). For example sounds/scream.au.
+ * Fetches the named resource from this bundle. The path should be specified as a relative,
+ * platform independent path (forward slashes). For example sounds/scream.au.
*
* @param path the path to the resource in this jar file.
*
- * @return an input stream from which the resource can be loaded or
- * null if no such resource exists.
+ * @return an input stream from which the resource can be loaded or null if no such resource
+ * exists.
*
- * @exception IOException thrown if an error occurs locating the
- * resource in the jar file.
+ * @exception IOException thrown if an error occurs locating the resource in the jar file.
*/
- public InputStream getResource (String path)
- throws IOException
- {
- // unpack our resources into a temp directory so that we can load
- // them quickly and the file system can cache them sensibly
- File rfile = getResourceFile(path);
- return (rfile == null) ? null : new FileInputStream(rfile);
- }
-
- /**
- * Returns a file from which the specified resource can be loaded.
- * This method will unpack the resource into a temporary directory and
- * return a reference to that file.
- *
- * @param path the path to the resource in this jar file.
- *
- * @return a file from which the resource can be loaded or null if no
- * such resource exists.
- */
- public File getResourceFile (String path)
- throws IOException
- {
- if (resolveJarFile()) {
- return null;
- }
-
- // if we have been unpacked, return our unpacked file
- if (_cache != null) {
- File cfile = new File(_cache, path);
- if (cfile.exists()) {
- return cfile;
- } else {
- return null;
- }
- }
-
- // otherwise, we unpack resources as needed into a temp directory
- String tpath = StringUtil.md5hex(_source.getPath() + "%" + path);
- File tfile = new File(getCacheDir(), tpath);
- if (tfile.exists() && (tfile.lastModified() > _sourceLastMod)) {
- return tfile;
- }
-
- JarEntry entry = _jarSource.getJarEntry(path);
- if (entry == null) {
-// Log.info("Couldn't locate " + path + " in " + _jarSource + ".");
- return null;
- }
-
- // copy the resource into the temporary file
- BufferedOutputStream fout = new BufferedOutputStream(new FileOutputStream(tfile));
- InputStream jin = _jarSource.getInputStream(entry);
- IOUtils.copy(jin, fout);
- jin.close();
- fout.close();
-
- return tfile;
- }
+ public abstract InputStream getResource (String path)
+ throws IOException;
/**
* Decodes and returns the specified image resource. Returns null if no resource exists at the
* specified path.
*/
- public BufferedImage getImageResource (String path)
- throws IOException
- {
- return ResourceManager.loadImage(getResourceFile(path));
- }
-
- /**
- * Returns true if this resource bundle contains the resource with the
- * specified path. This avoids actually loading the resource, in the
- * event that the caller only cares to know that the resource exists.
- */
- public boolean containsResource (String path)
- {
- try {
- if (resolveJarFile()) {
- return false;
- }
- return (_jarSource.getJarEntry(path) != null);
- } catch (IOException ioe) {
- return false;
- }
- }
-
- /**
- * Returns a string representation of this resource bundle.
- */
- public String toString ()
- {
- try {
- resolveJarFile();
- return (_jarSource == null) ? "[file=" + _source + "]" :
- "[path=" + _jarSource.getName() + "]";
-
- } catch (IOException ioe) {
- return "[file=" + _source + ", ioe=" + ioe + "]";
- }
- }
-
- /**
- * Creates the internal jar file reference if we've not already got
- * it; we do this lazily so as to avoid any jar- or zip-file-related
- * antics until and unless doing so is required, and because the
- * resource manager would like to be able to create bundles before the
- * associated files have been fully downloaded.
- *
- * @return true if the jar file could not yet be resolved because we
- * haven't yet heard from the resource manager that it is ready for us
- * to access, false if all is cool.
- */
- protected boolean resolveJarFile ()
- throws IOException
- {
- // if we don't yet have our resource bundle's last mod time, we
- // have not yet been notified that it is ready
- if (_sourceLastMod == -1) {
- return true;
- }
-
- if (!_source.exists()) {
- throw new IOException("Missing jar file for resource bundle: " +
- _source + ".");
- }
-
- try {
- if (_jarSource == null) {
- _jarSource = new JarFile(_source);
- }
- return false;
-
- } catch (IOException ioe) {
- String msg = "Failed to resolve resource bundle jar file '" +
- _source + "'";
- Log.warning(msg + ".");
- Log.logStackTrace(ioe);
- throw (IOException) new IOException(msg).initCause(ioe);
- }
- }
-
- /**
- * Closes our (possibly opened) jar file.
- */
- protected void closeJar ()
- {
- try {
- if (_jarSource != null) {
- _jarSource.close();
- }
- } catch (Exception ioe) {
- Log.warning("Failed to close jar file [path=" + _source +
- ", error=" + ioe + "].");
- }
- }
-
- /**
- * Returns the cache directory used for unpacked resources.
- */
- public static File getCacheDir ()
- {
- if (_tmpdir == null) {
- String tmpdir = System.getProperty("java.io.tmpdir");
- if (tmpdir == null) {
- Log.info("No system defined temp directory. Faking it.");
- tmpdir = System.getProperty("user.home");
- }
- setCacheDir(new File(tmpdir));
- }
- return _tmpdir;
- }
-
- /**
- * Specifies the directory in which our temporary resource files should be stored.
- */
- public static void setCacheDir (File tmpdir)
- {
- String rando = Long.toHexString((long)(Math.random() * Long.MAX_VALUE));
- _tmpdir = new File(tmpdir, "narcache_" + rando);
- if (!_tmpdir.exists()) {
- if (_tmpdir.mkdirs()) {
- Log.debug("Created narya temp cache directory '" + _tmpdir + "'.");
- } else {
- Log.warning("Failed to create temp cache directory '" + _tmpdir + "'.");
- }
- }
-
- // add a hook to blow away the temp directory when we exit
- Runtime.getRuntime().addShutdownHook(new Thread() {
- public void run () {
- Log.info("Clearing narya temp cache '" + _tmpdir + "'.");
- FileUtil.recursiveDelete(_tmpdir);
- }
- });
- }
-
- /** Strips the .jar off of jar file paths. */
- protected static String stripSuffix (String path)
- {
- if (path.endsWith(".jar")) {
- return path.substring(0, path.length()-4);
- } else {
- // we have to change the path somehow
- return path + "-cache";
- }
- }
-
- /** The file from which we construct our jar file. */
- protected File _source;
-
- /** The last modified time of our source jar file. */
- protected long _sourceLastMod = -1;
-
- /** A file whose timestamp indicates whether or not our existing jar
- * file has been unpacked. */
- protected File _unpacked;
-
- /** A directory into which we unpack files from our bundle. */
- protected File _cache;
-
- /** The jar file from which we load resources. */
- protected JarFile _jarSource;
-
- /** A directory in which we temporarily unpack our resource files. */
- protected static File _tmpdir;
+ public abstract BufferedImage getImageResource (String path)
+ throws IOException;
}
diff --git a/src/java/com/threerings/resource/ResourceManager.java b/src/java/com/threerings/resource/ResourceManager.java
index 023f4e33..e360562a 100644
--- a/src/java/com/threerings/resource/ResourceManager.java
+++ b/src/java/com/threerings/resource/ResourceManager.java
@@ -248,7 +248,7 @@ public class ResourceManager
}
// resolve the configured resource sets
- ArrayList dlist = new ArrayList();
+ List dlist = new ArrayList();
Enumeration names = config.propertyNames();
while (names.hasMoreElements()) {
String key = (String)names.nextElement();
@@ -326,7 +326,7 @@ public class ResourceManager
public boolean checkBundle (String path)
{
File bfile = getResourceFile(path);
- return (bfile == null) ? false : new ResourceBundle(bfile, true, _unpack).isUnpacked();
+ return (bfile == null) ? false : new FileResourceBundle(bfile, true, _unpack).isUnpacked();
}
/**
@@ -345,7 +345,7 @@ public class ResourceManager
return;
}
- final ResourceBundle bundle = new ResourceBundle(bfile, true, _unpack);
+ final FileResourceBundle bundle = new FileResourceBundle(bfile, true, _unpack);
if (bundle.isUnpacked()) {
if (bundle.sourceIsReady()) {
listener.requestCompleted(bundle);
@@ -404,8 +404,8 @@ public class ResourceManager
InputStream in = null;
// first look for this resource in our default resource bundle
- for (int i = 0; i < _default.length; i++) {
- in = _default[i].getResource(path);
+ for (ResourceBundle bundle : _default) {
+ in = bundle.getResource(path);
if (in != null) {
return in;
}
@@ -458,10 +458,10 @@ public class ResourceManager
throws IOException
{
// first look for this resource in our default resource bundle
- for (int i = 0; i < _default.length; i++) {
- File file = _default[i].getResourceFile(path);
- if (file != null) {
- return loadImage(file);
+ for (ResourceBundle bundle : _default) {
+ BufferedImage image = bundle.getImageResource(path);
+ if (image != null) {
+ return image;
}
}
@@ -624,13 +624,15 @@ public class ResourceManager
/**
* Loads up a resource set based on the supplied definition information.
*/
- protected void resolveResourceSet (String setName, String definition, List dlist)
+ protected void resolveResourceSet (
+ String setName, String definition, List dlist)
{
- ArrayList set = new ArrayList();
+ List set = new ArrayList();
StringTokenizer tok = new StringTokenizer(definition, ":");
while (tok.hasMoreTokens()) {
String path = tok.nextToken().trim();
- ResourceBundle bundle = new ResourceBundle(getResourceFile(path), true, _unpack);
+ FileResourceBundle bundle =
+ new FileResourceBundle(getResourceFile(path), true, _unpack);
set.add(bundle);
if (bundle.isUnpacked() && bundle.sourceIsReady()) {
continue;
@@ -639,8 +641,7 @@ public class ResourceManager
}
// convert our array list into an array and stick it in the table
- ResourceBundle[] setvec = new ResourceBundle[set.size()];
- set.toArray(setvec);
+ ResourceBundle[] setvec = set.toArray(new ResourceBundle[set.size()]);
_sets.put(setName, setvec);
// if this is our default resource bundle, keep a reference to it
@@ -693,7 +694,7 @@ public class ResourceManager
/** Used to unpack bundles on a separate thread. */
protected static class Unpacker extends Thread
{
- public Unpacker (List bundles, InitObserver obs)
+ public Unpacker (List bundles, InitObserver obs)
{
_bundles = bundles;
_obs = obs;
@@ -708,9 +709,9 @@ public class ResourceManager
}
int count = 0;
- for (Iterator iter = _bundles.iterator(); iter.hasNext(); ) {
- ResourceBundle bundle = (ResourceBundle)iter.next();
- if (!bundle.sourceIsReady()) {
+ for (ResourceBundle bundle : _bundles) {
+ if (bundle instanceof FileResourceBundle &&
+ !((FileResourceBundle)bundle).sourceIsReady()) {
Log.warning("Bundle failed to initialize " + bundle + ".");
}
if (_obs != null) {
@@ -732,7 +733,7 @@ public class ResourceManager
}
}
- protected List _bundles;
+ protected List _bundles;
protected InitObserver _obs;
}